From 4c1475936c73fb5d2bba713eed3a7b372356d3e5 Mon Sep 17 00:00:00 2001 From: Eric Reynolds Date: Thu, 27 May 2021 02:41:25 -0700 Subject: [PATCH 01/20] INS-674 - First pass at making the send button more visible (#3414) * INS-674 - First pass at making the send button more visible * [wip] adds rect for send button * Update colors in the SVG to match send button * Update the gRPC send button to also use theme colors * Adding left margin to send button Co-authored-by: Eric Reynolds Co-authored-by: Dimitri Mitropoulos Co-authored-by: Opender Singh --- .../app/ui/components/buttons/grpc-send-button.tsx | 3 ++- packages/insomnia-app/app/ui/components/settings/theme.tsx | 4 ++++ .../insomnia-app/app/ui/css/components/request-url-bar.less | 5 ++++- packages/insomnia-components/src/button/button.tsx | 5 +++++ 4 files changed, 15 insertions(+), 2 deletions(-) diff --git a/packages/insomnia-app/app/ui/components/buttons/grpc-send-button.tsx b/packages/insomnia-app/app/ui/components/buttons/grpc-send-button.tsx index 0e09def08..5a7ec9e02 100644 --- a/packages/insomnia-app/app/ui/components/buttons/grpc-send-button.tsx +++ b/packages/insomnia-app/app/ui/components/buttons/grpc-send-button.tsx @@ -12,8 +12,9 @@ interface Props { const buttonProps: ButtonProps = { className: 'tall', + bg: 'surprise', size: 'medium', - variant: 'text', + variant: 'contained', radius: '0', }; diff --git a/packages/insomnia-app/app/ui/components/settings/theme.tsx b/packages/insomnia-app/app/ui/components/settings/theme.tsx index 842a3d0f1..851007a1d 100644 --- a/packages/insomnia-app/app/ui/components/settings/theme.tsx +++ b/packages/insomnia-app/app/ui/components/settings/theme.tsx @@ -69,6 +69,10 @@ class Theme extends PureComponent { height="10%" className="theme--pane__header--sub bg-fill" /> + {/* Send Button */} + + + {/* Sidebar */} diff --git a/packages/insomnia-app/app/ui/css/components/request-url-bar.less b/packages/insomnia-app/app/ui/css/components/request-url-bar.less index dc9706ba7..f74d07b8c 100644 --- a/packages/insomnia-app/app/ui/css/components/request-url-bar.less +++ b/packages/insomnia-app/app/ui/css/components/request-url-bar.less @@ -26,8 +26,11 @@ .urlbar__send-btn { padding-right: var(--padding-md); padding-left: var(--padding-md); + margin-left: 0.75em; min-width: 5em; text-align: center; + background: var(--color-surprise); + color: #fff; } form { @@ -48,7 +51,7 @@ button:focus, button:hover { - background-color: var(--hl-xs); + filter: brightness(0.8); } & > .dropdown > button { diff --git a/packages/insomnia-components/src/button/button.tsx b/packages/insomnia-components/src/button/button.tsx index 31e307bd6..f9e6e334f 100644 --- a/packages/insomnia-components/src/button/button.tsx +++ b/packages/insomnia-components/src/button/button.tsx @@ -8,12 +8,17 @@ export const ButtonSizeEnum = { Medium: 'medium', } as const; +// These variants determine how the `bg` color variable is handled +// Outlined sets the `bg` color as an outline +// Contained sets the `bg` color as the background color +// Text sets the `bg` color as the text color export const ButtonVariantEnum = { Outlined: 'outlined', Contained: 'contained', Text: 'text', } as const; +// Sets the `bg` color to a themed color export const ButtonThemeEnum = { Default: 'default', Surprise: 'surprise', From c464e05728e0253d22427256a9e4781fa054cb64 Mon Sep 17 00:00:00 2001 From: Dimitri Mitropoulos Date: Thu, 27 May 2021 16:22:27 -0400 Subject: [PATCH 02/20] updates codemirror (in anticipation of upcoming PR bugfix) (#3405) Co-authored-by: Opender Singh --- packages/insomnia-app/app/codemirror.d.ts | 24 ++++++ .../ui/components/codemirror/code-editor.tsx | 39 ++++----- .../codemirror/extensions/autocomplete.ts | 9 ++- .../codemirror/extensions/clickable.ts | 4 +- .../codemirror/extensions/nunjucks-tags.ts | 80 ++++++++++--------- .../ui/components/codemirror/lint/openapi.ts | 6 +- .../components/codemirror/one-line-editor.tsx | 4 +- .../editors/body/graph-ql-editor.tsx | 1 - packages/insomnia-app/package-lock.json | 69 +++++++++------- packages/insomnia-app/package.json | 6 +- 10 files changed, 135 insertions(+), 107 deletions(-) create mode 100644 packages/insomnia-app/app/codemirror.d.ts diff --git a/packages/insomnia-app/app/codemirror.d.ts b/packages/insomnia-app/app/codemirror.d.ts new file mode 100644 index 000000000..07db72ccf --- /dev/null +++ b/packages/insomnia-app/app/codemirror.d.ts @@ -0,0 +1,24 @@ +import 'codemirror'; +import { HandleGetRenderContext, HandleRender } from './common/render'; + +type LinkClickCallback = (url: string) => void; + +interface InsomniaExtensions { + closeHintDropdown: () => void; + enableNunjucksTags: ( + handleRender: HandleRender, + handleGetRenderContext?: HandleGetRenderContext, + isVariableUncovered?: boolean, + ) => void; + isHintDropdownActive: () => boolean; + makeLinksClickable: (handleClick: LinkClickCallback) => void; +} + +declare module 'codemirror' { + type CodeMirrorLinkClickCallback = LinkClickCallback; + + /* eslint-disable @typescript-eslint/no-empty-interface */ + interface Editor extends InsomniaExtensions {} + interface EditorFromTextEditor extends InsomniaExtensions {} + /* eslint-enable @typescript-eslint/no-empty-interface */ +} diff --git a/packages/insomnia-app/app/ui/components/codemirror/code-editor.tsx b/packages/insomnia-app/app/ui/components/codemirror/code-editor.tsx index 0cb5139c3..5834985bf 100644 --- a/packages/insomnia-app/app/ui/components/codemirror/code-editor.tsx +++ b/packages/insomnia-app/app/ui/components/codemirror/code-editor.tsx @@ -6,7 +6,7 @@ import { EDITOR_KEY_MAP_VIM, isMac, } from '../../../common/constants'; -import CodeMirror from 'codemirror'; +import CodeMirror, { CodeMirrorLinkClickCallback } from 'codemirror'; import classnames from 'classnames'; import clone from 'clone'; import jq from 'jsonpath'; @@ -24,7 +24,6 @@ import DropdownItem from '../base/dropdown/dropdown-item'; import { query as queryXPath } from 'insomnia-xpath'; import deepEqual from 'deep-equal'; import zprint from 'zprint-clj'; -import { CodeMirrorLinkClickCallback } from './extensions/clickable'; import { HandleGetRenderContext, HandleRender } from '../../../common/render'; import { NunjucksParsedTag } from '../../../templating/utils'; const TAB_KEY = 9; @@ -167,7 +166,6 @@ class CodeEditor extends Component { componentWillUnmount() { if (this.codeMirror) { this.codeMirror.toTextArea(); - // @ts-expect-error -- TSCONVERSION this comes from a custom extension this.codeMirror.closeHintDropdown(); } } @@ -249,7 +247,7 @@ class CodeEditor extends Component { } } - setSelection(chStart, chEnd, lineStart, lineEnd) { + setSelection(chStart: number, chEnd: number, lineStart: number, lineEnd: number) { if (this.codeMirror) { this.codeMirror.setSelection( { @@ -262,14 +260,13 @@ class CodeEditor extends Component { }, ); this.codeMirror.scrollIntoView({ - // @ts-expect-error -- TSCONVERSION line: lineStart, - char: chStart, + ch: chStart, }); } } - scrollToSelection(chStart, chEnd, lineStart, lineEnd) { + scrollToSelection(chStart: number, chEnd: number, lineStart: number, lineEnd: number) { const selectionFocusPos = window.innerHeight / 2 - 100; if (this.codeMirror) { @@ -285,9 +282,8 @@ class CodeEditor extends Component { ); this.codeMirror.scrollIntoView( { - // @ts-expect-error -- TSCONVERSION line: lineStart, - char: chStart, + ch: chStart, }, // If sizing permits, position selection just above center selectionFocusPos, ); @@ -352,8 +348,7 @@ class CodeEditor extends Component { clearSelection() { // Never do this if dropdown is open - // @ts-expect-error -- TSCONVERSION this comes from a custom extension - if (this.codeMirror.isHintDropdownActive()) { + if (this.codeMirror?.isHintDropdownActive()) { return; } @@ -430,7 +425,7 @@ class CodeEditor extends Component { scroll: false, }); // @ts-expect-error -- TSCONVERSION - this.codeMirror.setSelections(selections, null, { + this.codeMirror.setSelection(selections, null, { scroll: false, }); @@ -503,7 +498,9 @@ class CodeEditor extends Component { this.codeMirror.on('blur', this._codemirrorBlur); this.codeMirror.on('paste', this._codemirrorPaste); this.codeMirror.on('scroll', this._codemirrorScroll); + // @ts-expect-error this event does indeed exist, but is not present on the CodeMirror types and declaration merging doesn't seem to want to allow adding it this.codeMirror.on('fold', this._codemirrorToggleFold); + // @ts-expect-error this event does indeed exist, but is not present on the CodeMirror types and declaration merging doesn't seem to want to allow adding it this.codeMirror.on('unfold', this._codemirrorToggleFold); this.codeMirror.on('keyHandled', this._codemirrorKeyHandled); // Prevent these things if we're type === "password" @@ -536,13 +533,11 @@ class CodeEditor extends Component { this._codemirrorSetValue(defaultValue || ''); // Clear history so we can't undo the initial set - // @ts-expect-error -- TSCONVERSION - this.codeMirror.clearHistory(); + this.codeMirror?.clearHistory(); // Setup nunjucks listeners if (this.props.render && !this.props.nunjucksPowerUserMode) { - // @ts-expect-error -- TSCONVERSION this comes from a custom extension - this.codeMirror.enableNunjucksTags( + this.codeMirror?.enableNunjucksTags( this.props.render, this.props.getRenderContext, this.props.isVariableUncovered, @@ -551,15 +546,12 @@ class CodeEditor extends Component { // Make URLs clickable if (this.props.onClickLink) { - // @ts-expect-error -- TSCONVERSION this comes from a custom extension - this.codeMirror.makeLinksClickable(this.props.onClickLink); + this.codeMirror?.makeLinksClickable(this.props.onClickLink); } - // HACK: Refresh because sometimes it renders too early and the scroll doesn't - // quite fit. + // HACK: Refresh because sometimes it renders too early and the scroll doesn't quite fit. setTimeout(() => { - // @ts-expect-error -- TSCONVERSION - this.codeMirror.refresh(); + this.codeMirror?.refresh(); }, 100); // Restore the state @@ -908,7 +900,7 @@ class CodeEditor extends Component { } } - async _codemirrorKeyDown(doc, e) { + async _codemirrorKeyDown(doc: CodeMirror.EditorFromTextArea, e) { // Use default tab behaviour if we're told if (this.props.defaultTabBehavior && e.keyCode === TAB_KEY) { e.codemirrorIgnore = true; @@ -1037,7 +1029,6 @@ class CodeEditor extends Component { if (shouldLint !== existingLint) { const { lintOptions } = this.props; const lint = shouldLint ? lintOptions || true : false; - this._codemirrorSmartSetOption('lint', lint); } diff --git a/packages/insomnia-app/app/ui/components/codemirror/extensions/autocomplete.ts b/packages/insomnia-app/app/ui/components/codemirror/extensions/autocomplete.ts index 333ff9929..1906ca172 100644 --- a/packages/insomnia-app/app/ui/components/codemirror/extensions/autocomplete.ts +++ b/packages/insomnia-app/app/ui/components/codemirror/extensions/autocomplete.ts @@ -3,6 +3,7 @@ import 'codemirror/addon/mode/overlay'; import * as models from '../../../../models'; import { getDefaultFill } from '../../../../templating/utils'; import { escapeHTML, escapeRegex } from '../../../../common/misc'; + const NAME_MATCH_FLEXIBLE = /[\w.\][\-/]+$/; const NAME_MATCH = /[\w.\][]+$/; const AFTER_VARIABLE_MATCH = /{{\s*[\w.\][]*$/; @@ -37,6 +38,7 @@ const ICONS = { title: 'Generator Tag', }, }; + CodeMirror.defineExtension('isHintDropdownActive', function() { return ( this.state.completionActive && @@ -45,15 +47,17 @@ CodeMirror.defineExtension('isHintDropdownActive', function() { this.state.completionActive.data.list.length ); }); + CodeMirror.defineExtension('closeHintDropdown', function() { this.state.completionActive?.close(); }); + CodeMirror.defineOption('environmentAutocomplete', null, (cm, options) => { if (!options) { return; } - async function completeAfter(cm, fn, showAllOnNoMatch = false) { + async function completeAfter(cm: CodeMirror.Editor, fn, showAllOnNoMatch = false) { // Bail early if didn't match the callback test if (fn && !fn()) { return; @@ -68,7 +72,7 @@ CodeMirror.defineOption('environmentAutocomplete', null, (cm, options) => { return; } - let hintsContainer = document.querySelector('#hints-container'); + let hintsContainer = document.querySelector('#hints-container'); if (!hintsContainer) { const el = document.createElement('div'); @@ -85,6 +89,7 @@ CodeMirror.defineOption('environmentAutocomplete', null, (cm, options) => { // Actually show the hint cm.showHint({ // Insomnia-specific options + // @ts-expect-error -- TSCONVERSION needs investigation constants: constants || [], variables: variables || [], snippets: snippets || [], diff --git a/packages/insomnia-app/app/ui/components/codemirror/extensions/clickable.ts b/packages/insomnia-app/app/ui/components/codemirror/extensions/clickable.ts index 7e8a3344a..7135c64e1 100644 --- a/packages/insomnia-app/app/ui/components/codemirror/extensions/clickable.ts +++ b/packages/insomnia-app/app/ui/components/codemirror/extensions/clickable.ts @@ -1,11 +1,9 @@ -import CodeMirror from 'codemirror'; +import CodeMirror, { CodeMirrorLinkClickCallback } from 'codemirror'; import 'codemirror/addon/mode/overlay'; import { AllHtmlEntities } from 'html-entities'; import { FLEXIBLE_URL_REGEX } from '../../../../common/constants'; const entities = new AllHtmlEntities(); -export type CodeMirrorLinkClickCallback = (url: string) => void; - CodeMirror.defineExtension('makeLinksClickable', function(handleClick: CodeMirrorLinkClickCallback) { // Only add the click mode if we have links to click this.addOverlay({ diff --git a/packages/insomnia-app/app/ui/components/codemirror/extensions/nunjucks-tags.ts b/packages/insomnia-app/app/ui/components/codemirror/extensions/nunjucks-tags.ts index c322afc84..8d7083a15 100644 --- a/packages/insomnia-app/app/ui/components/codemirror/extensions/nunjucks-tags.ts +++ b/packages/insomnia-app/app/ui/components/codemirror/extensions/nunjucks-tags.ts @@ -4,40 +4,43 @@ import NunjucksVariableModal from '../../modals/nunjucks-modal'; import { showModal } from '../../modals/index'; import { tokenizeTag } from '../../../../templating/utils'; import { getTagDefinitions } from '../../../../templating/index'; +import { HandleGetRenderContext, HandleRender } from '../../../../common/render'; -CodeMirror.defineExtension( - 'enableNunjucksTags', - function(handleRender, handleGetRenderContext, isVariableUncovered = false) { - if (!handleRender) { - console.warn("enableNunjucksTags wasn't passed a render function"); - return; +CodeMirror.defineExtension('enableNunjucksTags', function( + handleRender: HandleRender, + handleGetRenderContext: HandleGetRenderContext, + isVariableUncovered = false, +) { + if (!handleRender) { + console.warn("enableNunjucksTags wasn't passed a render function"); + return; + } + + const refreshFn = _highlightNunjucksTags.bind( + this, + handleRender, + handleGetRenderContext, + isVariableUncovered, + ); + + const debouncedRefreshFn = misc.debounce(refreshFn); + this.on('change', (_cm, change) => { + const origin = change.origin || 'unknown'; + + if (!origin.match(/^[+*]/)) { + // Refresh immediately on non-joinable events + // (cut, paste, autocomplete; as opposed to +input, +delete) + refreshFn(); + } else { + // Debounce all joinable events + debouncedRefreshFn(); } - - const refreshFn = _highlightNunjucksTags.bind( - this, - handleRender, - handleGetRenderContext, - isVariableUncovered, - ); - - const debouncedRefreshFn = misc.debounce(refreshFn); - this.on('change', (_cm, change) => { - const origin = change.origin || 'unknown'; - - if (!origin.match(/^[+*]/)) { - // Refresh immediately on non-joinable events - // (cut, paste, autocomplete; as opposed to +input, +delete) - refreshFn(); - } else { - // Debounce all joinable events - debouncedRefreshFn(); - } - }); - this.on('cursorActivity', debouncedRefreshFn); - this.on('viewportChange', debouncedRefreshFn); - // Trigger once right away to snappy perf - refreshFn(); - }, + }); + this.on('cursorActivity', debouncedRefreshFn); + this.on('viewportChange', debouncedRefreshFn); + // Trigger once right away to snappy perf + refreshFn(); +}, ); async function _highlightNunjucksTags(render, renderContext, isVariableUncovered) { @@ -186,13 +189,12 @@ async function _highlightNunjucksTags(render, renderContext, isVariableUncovered // Set up the drag el.addEventListener('dragstart', event => { // Setup the drag contents - // @ts-expect-error -- TSCONVERSION - const template = event.target?.getAttribute('data-template'); - event.dataTransfer?.setData('text/plain', template); - // @ts-expect-error -- TSCONVERSION - event.dataTransfer.effectAllowed = 'copyMove'; - // @ts-expect-error -- TSCONVERSION - event.dataTransfer.dropEffect = 'move'; + if (event.dataTransfer) { + const template = (event.target as typeof el)?.getAttribute('data-template') || ''; + event.dataTransfer.setData('text/plain', template); + event.dataTransfer.effectAllowed = 'copyMove'; + event.dataTransfer.dropEffect = 'move'; + } // Add some listeners this.on('beforeChange', beforeChangeCb); this.on('drop', dropCb); diff --git a/packages/insomnia-app/app/ui/components/codemirror/lint/openapi.ts b/packages/insomnia-app/app/ui/components/codemirror/lint/openapi.ts index b606967c0..757285674 100644 --- a/packages/insomnia-app/app/ui/components/codemirror/lint/openapi.ts +++ b/packages/insomnia-app/app/ui/components/codemirror/lint/openapi.ts @@ -6,10 +6,8 @@ const spectral = new Spectral(); CodeMirror.registerHelper('lint', 'openapi', async function(text) { const results = await spectral.run(text); return results.map(result => ({ - // @ts-expect-error -- TSCONVERSION - from: CodeMirror.Pos(result.range.start.line, result.range.start.chracter), - // @ts-expect-error -- TSCONVERSION - to: CodeMirror.Pos(result.range.end.line, result.range.end.chracter), + from: CodeMirror.Pos(result.range.start.line, result.range.start.character), + to: CodeMirror.Pos(result.range.end.line, result.range.end.character), message: result.message, })); }); diff --git a/packages/insomnia-app/app/ui/components/codemirror/one-line-editor.tsx b/packages/insomnia-app/app/ui/components/codemirror/one-line-editor.tsx index d21a50bd2..556be6059 100644 --- a/packages/insomnia-app/app/ui/components/codemirror/one-line-editor.tsx +++ b/packages/insomnia-app/app/ui/components/codemirror/one-line-editor.tsx @@ -277,8 +277,10 @@ class OneLineEditor extends PureComponent { if (this._input?.hasFocus()) { const start = this._input?.getSelectionStart(); - const end = this._input?.getSelectionEnd(); + if (start === null || end === null) { + return; + } // Wait for the editor to swap and restore cursor position const check = () => { diff --git a/packages/insomnia-app/app/ui/components/editors/body/graph-ql-editor.tsx b/packages/insomnia-app/app/ui/components/editors/body/graph-ql-editor.tsx index 5c303e362..524cd2d74 100644 --- a/packages/insomnia-app/app/ui/components/editors/body/graph-ql-editor.tsx +++ b/packages/insomnia-app/app/ui/components/editors/body/graph-ql-editor.tsx @@ -7,7 +7,6 @@ import type { GraphQLArgument, GraphQLField, GraphQLSchema, GraphQLType } from ' import { parse, print, typeFromAST } from 'graphql'; import { introspectionQuery } from 'graphql/utilities/introspectionQuery'; import { buildClientSchema } from 'graphql/utilities/buildClientSchema'; -// import type { CodeMirror, TextMarker } from 'codemirror'; import CodeEditor from '../../codemirror/code-editor'; import { jsonParseOr } from '../../../../common/misc'; import HelpTooltip from '../../help-tooltip'; diff --git a/packages/insomnia-app/package-lock.json b/packages/insomnia-app/package-lock.json index 1f52064ab..3c09f79f2 100644 --- a/packages/insomnia-app/package-lock.json +++ b/packages/insomnia-app/package-lock.json @@ -3909,9 +3909,9 @@ "dev": true }, "@types/codemirror": { - "version": "0.0.109", - "resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-0.0.109.tgz", - "integrity": "sha512-cSdiHeeLjvGn649lRTNeYrVCDOgDrtP+bDDSFDd1TF+i0jKGPDRozno2NOJ9lTniso+taiv4kiVS8dgM8Jm5lg==", + "version": "5.60.0", + "resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-5.60.0.tgz", + "integrity": "sha512-xgzXZyCzedLRNC67/Nn8rpBtTFnAsX2C+Q/LGoH6zgcpF/LqdNHJMHEOhqT1bwUcSp6kQdOIuKzRbeW9DYhEhg==", "dev": true, "requires": { "@types/tern": "*" @@ -8244,17 +8244,17 @@ "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" }, "codemirror": { - "version": "5.56.0", - "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.56.0.tgz", - "integrity": "sha512-MfKVmYgifXjQpLSgpETuih7A7WTTIsxvKfSLGseTY5+qt0E1UD1wblZGM6WLenORo8sgmf+3X+WTe2WF7mufyw==" + "version": "5.61.0", + "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.61.0.tgz", + "integrity": "sha512-D3wYH90tYY1BsKlUe0oNj2JAhQ9TepkD51auk3N7q+4uz7A/cgJ5JsWHreT0PqieW1QhOuqxQ2reCXV1YXzecg==" }, "codemirror-graphql": { - "version": "0.11.6", - "resolved": "https://registry.npmjs.org/codemirror-graphql/-/codemirror-graphql-0.11.6.tgz", - "integrity": "sha512-/zVKgOVS2/hfjAY0yoBkLz9ESHnWKBWpBNXQSoFF4Hl5q5AS2DmM22coonWKJcCvNry6TLak2F+QkzPeKVv3Eg==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/codemirror-graphql/-/codemirror-graphql-1.0.1.tgz", + "integrity": "sha512-5ttMpv2kMn99Rmf2aZ5P6/hMd3y11cN8LP/x5MUeF0ipcalZA/GE/OxxXkhV0YJE/uW5QIcPyZDkvtSsGZa23A==", "requires": { - "graphql-language-service-interface": "^2.3.3", - "graphql-language-service-parser": "^1.5.2" + "graphql-language-service-interface": "^2.8.2", + "graphql-language-service-parser": "^1.9.0" } }, "collect-v8-coverage": { @@ -13260,32 +13260,36 @@ } }, "graphql-language-service-interface": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/graphql-language-service-interface/-/graphql-language-service-interface-2.4.0.tgz", - "integrity": "sha512-r7DQPyhCFY5TlpEukdh9tekJ9hAc7MD9TdOsb5CfAPlsIb1/faVVo2Ty19PxGSYDxygXjwpKLOQD0LqqFuw63A==", + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/graphql-language-service-interface/-/graphql-language-service-interface-2.8.3.tgz", + "integrity": "sha512-Gh4Q3dlCT1MrZGO0eaz7v31gkp8fh+ig94YH/A+1Th2q+k3RsRqfSJm5tKZ8TJ4rSADZ/dj+hzOpWCGzLyCiHQ==", "requires": { - "graphql-language-service-parser": "^1.6.0", - "graphql-language-service-types": "^1.6.0", - "graphql-language-service-utils": "^2.4.0", + "graphql-language-service-parser": "^1.9.0", + "graphql-language-service-types": "^1.8.0", + "graphql-language-service-utils": "^2.5.1", "vscode-languageserver-types": "^3.15.1" } }, "graphql-language-service-parser": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/graphql-language-service-parser/-/graphql-language-service-parser-1.6.0.tgz", - "integrity": "sha512-tkfYXl6WBECWNcsyw7O09c0oCMrxqr3JGyDNvdISDSDhvk8EwEk+AOweBEJkycJwoiv1lVlM+EdLfj7dzw4/6w==" + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/graphql-language-service-parser/-/graphql-language-service-parser-1.9.1.tgz", + "integrity": "sha512-GySsDrYxzxu6r1vF282xXDR2KlfVL5aOW7pgc75fF3UFiuqGm/SeoIljNM0mLpRl5KSxo1HNOxhkWoFBoy/h2w==", + "requires": { + "graphql-language-service-types": "^1.8.0" + } }, "graphql-language-service-types": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/graphql-language-service-types/-/graphql-language-service-types-1.6.0.tgz", - "integrity": "sha512-7y4pkhTL+MtarC8CeHpvRYVc6pkT6ITdQXs+Gr7OClnk4Lz5nQEK0eO29kUdd0jEnYfHyh5iqhnL5Owfy+wT0A==" + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/graphql-language-service-types/-/graphql-language-service-types-1.8.1.tgz", + "integrity": "sha512-IpYS0mEHEmRsFlq+loWCpSYYYizAID7Alri6GoFN1QqUdux+8rp1Tkp2NGsGDpDmm3Dbz5ojmJWzNWQGpuwveA==" }, "graphql-language-service-utils": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/graphql-language-service-utils/-/graphql-language-service-utils-2.4.0.tgz", - "integrity": "sha512-aLa+8iqb7AJYgdcawsKH8/KLc14DHcRsnveshOm8hN6bBVT0YiQP6mEfJoci741O74uaDF2zj1J3c02vorichw==", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/graphql-language-service-utils/-/graphql-language-service-utils-2.5.2.tgz", + "integrity": "sha512-hXGd4ARhyD7WTmTwuYmCYo6BcY8FtTp+1JHLaUG0Q63k0NpZTuFuRZ+N7TSP9mcRb7labeozs3DYgaqStsDe1A==", "requires": { - "graphql-language-service-types": "^1.6.0" + "graphql-language-service-types": "^1.8.0", + "nullthrows": "^1.0.0" } }, "growly": { @@ -18921,6 +18925,11 @@ "boolbase": "~1.0.0" } }, + "nullthrows": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/nullthrows/-/nullthrows-1.1.1.tgz", + "integrity": "sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==" + }, "num2fraction": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/num2fraction/-/num2fraction-1.2.2.tgz", @@ -25364,9 +25373,9 @@ "dev": true }, "vscode-languageserver-types": { - "version": "3.15.1", - "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.15.1.tgz", - "integrity": "sha512-+a9MPUQrNGRrGU630OGbYVQ+11iOIovjCkqxajPa9w57Sd5ruK8WQNsslzpa0x/QJqC8kRc2DUxWjIFwoNm4ZQ==" + "version": "3.16.0", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.16.0.tgz", + "integrity": "sha512-k8luDIWJWyenLc5ToFQQMaSrqCHiLwyKPHKPQZ5zz21vM+vIVUSvsRpcbiECH4WR88K2XZqc4ScRcZ7nk/jbeA==" }, "w3c-hr-time": { "version": "1.0.2", diff --git a/packages/insomnia-app/package.json b/packages/insomnia-app/package.json index 9da7cd6af..c69eb1caf 100644 --- a/packages/insomnia-app/package.json +++ b/packages/insomnia-app/package.json @@ -81,8 +81,8 @@ "class-autobind-decorator": "^3.0.1", "classnames": "^2.3.1", "clone": "^2.1.0", - "codemirror": "^5.50.0", - "codemirror-graphql": "^0.11.3", + "codemirror": "^5.61.0", + "codemirror-graphql": "^1.0.1", "color": "^3.1.2", "deep-equal": "^1.0.1", "electron-context-menu": "^2.2.0", @@ -176,7 +176,7 @@ "@types/analytics-node": "^3.1.4", "@types/aws4": "^1.5.1", "@types/clone": "^2.1.0", - "@types/codemirror": "^0.0.109", + "@types/codemirror": "^5.60.0", "@types/color": "^3.0.1", "@types/concurrently": "^6.0.1", "@types/deep-equal": "^1.0.1", From f6504aaf8952fc19cbce4f5682dfd8c107ff32af Mon Sep 17 00:00:00 2001 From: Vincenzo De Petris <37916223+vincendep@users.noreply.github.com> Date: Thu, 27 May 2021 22:36:28 +0200 Subject: [PATCH 03/20] Fix issue chaining multiple requests (#3385) * Fix issue chaining multiple requests * refactor: use switch to prepare for leveraging exhaustiveness checking since this will be in TypeScript soon, and this was as topical a time as any to make this change. * fixes failing test with new behavior (and removes unused `fromResponseTag`) * fix tests Co-authored-by: Vincenzo De Petris Co-authored-by: Dimitri Mitropoulos Co-authored-by: Opender Singh --- .../__tests__/index.test.js | 18 ++- plugins/insomnia-plugin-response/index.js | 110 ++++++++++-------- 2 files changed, 72 insertions(+), 56 deletions(-) diff --git a/plugins/insomnia-plugin-response/__tests__/index.test.js b/plugins/insomnia-plugin-response/__tests__/index.test.js index 6828005c7..b47cd2a31 100644 --- a/plugins/insomnia-plugin-response/__tests__/index.test.js +++ b/plugins/insomnia-plugin-response/__tests__/index.test.js @@ -540,12 +540,10 @@ describe('Response tag', () => { expect(await tag.run(context, 'raw', 'req_1', '', 'never')).toBe('Response res_existing'); }); - it('does not resend recursive', async () => { + it('does not resend if request has already sent in recursive chain', async () => { const requests = [{ _id: 'req_1', parentId: 'wrk_1' }]; - const responses = []; - - const context = _genTestContext(requests, responses, { fromResponseTag: true }); + const context = _genTestContext(requests, responses, { requestChain: ['req_1']}); try { await tag.run(context, 'raw', 'req_1', '', 'always'); @@ -555,9 +553,19 @@ describe('Response tag', () => { } throw new Error('Running tag should have thrown exception'); - }); }); + it('does send if request has not been sent in recursive chain', async () => { + const requests = [{ _id: 'req_1', parentId: 'wrk_1' }]; + const responses = []; + + const context = _genTestContext(requests, responses, { requestChain: ['req_2']}); + + const response = await tag.run(context, 'raw', 'req_1', '', 'always'); + expect(response).toBe('Response res_1') + }); +}); + describe('Max Age', () => { const maxAgeArg = tag.args[4]; const toValueObj = value => ({ value }); diff --git a/plugins/insomnia-plugin-response/index.js b/plugins/insomnia-plugin-response/index.js index a79d6c81f..cc0461518 100644 --- a/plugins/insomnia-plugin-response/index.js +++ b/plugins/insomnia-plugin-response/index.js @@ -121,34 +121,43 @@ module.exports.templateTags = [ let response = await context.util.models.response.getLatestForRequestId(id, environmentId); let shouldResend = false; - if (context.context.getExtraInfo('fromResponseTag')) { - shouldResend = false; - } else if (resendBehavior === 'never') { - shouldResend = false; - } else if (resendBehavior === 'no-history') { - shouldResend = !response; - } else if (resendBehavior === 'when-expired') { - if (!response) { + switch (resendBehavior) { + case 'no-history': + shouldResend = !response; + break; + + case 'when-expired': + if (!response) { + shouldResend = true; + } else { + const ageSeconds = (Date.now() - response.created) / 1000; + shouldResend = ageSeconds > maxAgeSeconds; + } + break; + + case 'always': shouldResend = true; - } else { - const ageSeconds = (Date.now() - response.created) / 1000; - shouldResend = ageSeconds > maxAgeSeconds; - } - } else if (resendBehavior === 'always') { - shouldResend = true; + break; + + case 'never': + default: + shouldResend = false; + break; + } // Make sure we only send the request once per render so we don't have infinite recursion - const fromResponseTag = context.context.getExtraInfo('fromResponseTag'); - if (fromResponseTag) { + const requestChain = context.context.getExtraInfo('requestChain') || []; + if (requestChain.some(id => id === request._id)) { console.log('[response tag] Preventing recursive render'); shouldResend = false; } if (shouldResend && context.renderPurpose === 'send') { console.log('[response tag] Resending dependency'); + requestChain.push(request._id) response = await context.network.sendRequest(request, [ - { name: 'fromResponseTag', value: true }, + { name: 'requestChain', value: requestChain } ]); } @@ -172,44 +181,43 @@ module.exports.templateTags = [ } const sanitizedFilter = filter.trim(); + const bodyBuffer = context.util.models.response.getBodyBuffer(response, ''); + const match = response.contentType && response.contentType.match(/charset=([\w-]+)/); + const charset = match && match.length >= 2 ? match[1] : 'utf-8'; + switch (field) { + case 'header': + return matchHeader(response.headers, sanitizedFilter); - if (field === 'header') { - return matchHeader(response.headers, sanitizedFilter); - } else if (field === 'url') { - return response.url; - } else if (field === 'raw') { - const bodyBuffer = context.util.models.response.getBodyBuffer(response, ''); - const match = response.contentType.match(/charset=([\w-]+)/); - const charset = match && match.length >= 2 ? match[1] : 'utf-8'; + case 'url': + return response.url; - // Sometimes iconv conversion fails so fallback to regular buffer - try { - return iconv.decode(bodyBuffer, charset); - } catch (err) { - console.warn('[response] Failed to decode body', err); - return bodyBuffer.toString(); - } - } else if (field === 'body') { - const bodyBuffer = context.util.models.response.getBodyBuffer(response, ''); - const match = response.contentType.match(/charset=([\w-]+)/); - const charset = match && match.length >= 2 ? match[1] : 'utf-8'; + case 'raw': + // Sometimes iconv conversion fails so fallback to regular buffer + try { + return iconv.decode(bodyBuffer, charset); + } catch (err) { + console.warn('[response] Failed to decode body', err); + return bodyBuffer.toString(); + } - // Sometimes iconv conversion fails so fallback to regular buffer - let body; - try { - body = iconv.decode(bodyBuffer, charset); - } catch (err) { - body = bodyBuffer.toString(); - console.warn('[response] Failed to decode body', err); - } + case 'body': + // Sometimes iconv conversion fails so fallback to regular buffer + let body; + try { + body = iconv.decode(bodyBuffer, charset); + } catch (err) { + body = bodyBuffer.toString(); + console.warn('[response] Failed to decode body', err); + } + + if (sanitizedFilter.indexOf('$') === 0) { + return matchJSONPath(body, sanitizedFilter); + } else { + return matchXPath(body, sanitizedFilter); + } - if (sanitizedFilter.indexOf('$') === 0) { - return matchJSONPath(body, sanitizedFilter); - } else { - return matchXPath(body, sanitizedFilter); - } - } else { - throw new Error(`Unknown field ${field}`); + default: + throw new Error(`Unknown field ${field}`); } }, }, From 7861d7a5052522e7d0a37aa4aeea61993ba65bd6 Mon Sep 17 00:00:00 2001 From: Dimitri Mitropoulos Date: Wed, 2 Jun 2021 08:55:31 -0400 Subject: [PATCH 04/20] [o2k] all Kong entities have should tags (#3428) --- packages/openapi-2-kong/README.md | 145 +++++--- packages/openapi-2-kong/src/common.test.ts | 5 +- packages/openapi-2-kong/src/common.ts | 10 +- .../fixtures/api-with-examples.yaml | 200 +++++------ .../fixtures/httpbin.expected.json | 69 ++-- .../declarative-config/fixtures/httpbin.yaml | 22 +- .../fixtures/link-example.expected.json | 16 +- .../fixtures/link-example.yaml | 116 +++---- .../fixtures/no-targets-example.yaml | 2 +- .../fixtures/petstore-expanded.expected.json | 4 +- .../fixtures/petstore-expanded.yaml | 6 +- .../fixtures/petstore.expected.json | 2 +- .../declarative-config/fixtures/petstore.yaml | 16 +- .../request-validator-plugin.expected.json | 78 +---- .../fixtures/security.expected.json | 40 ++- .../declarative-config/fixtures/security.yaml | 20 +- .../fixtures/uspto.expected.json | 4 +- .../declarative-config/fixtures/uspto.yaml | 35 +- .../src/declarative-config/generate.ts | 5 +- .../declarative-config/jest/test-helpers.ts | 90 +++++ .../src/declarative-config/names.test.ts | 3 +- .../src/declarative-config/plugins.test.ts | 153 ++++----- .../src/declarative-config/plugins.ts | 81 ++--- .../security-plugins.test.ts | 9 +- .../declarative-config/security-plugins.ts | 44 ++- .../src/declarative-config/services.test.ts | 196 +++++------ .../src/declarative-config/services.ts | 3 +- .../src/declarative-config/tags.test.ts | 61 ++++ .../src/declarative-config/upstreams.test.ts | 31 +- .../src/declarative-config/upstreams.ts | 3 +- .../src/declarative-config/utils.ts | 36 -- packages/openapi-2-kong/src/generate.test.ts | 4 +- packages/openapi-2-kong/src/generate.ts | 92 +++--- packages/openapi-2-kong/src/index.ts | 1 - .../fixtures/cloud-api.expected.yaml | 13 +- .../src/kubernetes/fixtures/cloud-api.yaml | 88 ++--- .../src/kubernetes/generate.test.ts | 120 +++---- .../openapi-2-kong/src/kubernetes/generate.ts | 2 +- .../src/kubernetes/plugin-helpers.ts | 32 +- .../src/kubernetes/plugins.test.ts | 29 +- .../openapi-2-kong/src/kubernetes/plugins.ts | 25 +- .../src/kubernetes/variables.test.ts | 12 +- .../src/kubernetes/variables.ts | 6 +- .../src/types/declarative-config.ts | 49 +-- packages/openapi-2-kong/src/types/index.ts | 2 +- .../types/{k8splugins.ts => k8s-plugins.ts} | 0 packages/openapi-2-kong/src/types/kong.ts | 311 ++++++++++++++++++ .../src/types/kubernetes-config.ts | 43 +-- packages/openapi-2-kong/src/types/openapi3.ts | 140 ++++---- packages/openapi-2-kong/src/types/outputs.ts | 10 +- packages/openapi-2-kong/tsconfig.build.json | 3 +- 51 files changed, 1445 insertions(+), 1042 deletions(-) create mode 100644 packages/openapi-2-kong/src/declarative-config/jest/test-helpers.ts create mode 100644 packages/openapi-2-kong/src/declarative-config/tags.test.ts delete mode 100644 packages/openapi-2-kong/src/declarative-config/utils.ts rename packages/openapi-2-kong/src/types/{k8splugins.ts => k8s-plugins.ts} (100%) create mode 100644 packages/openapi-2-kong/src/types/kong.ts diff --git a/packages/openapi-2-kong/README.md b/packages/openapi-2-kong/README.md index 3d8733911..b2de61265 100644 --- a/packages/openapi-2-kong/README.md +++ b/packages/openapi-2-kong/README.md @@ -2,38 +2,70 @@ This module generates Kong Declarative Config and Kong for Kubernetes config, from OpenAPI 3.0 specifications. -**Table of Contents** - -- [Library Usage](#library-usage) -- [Kong Declarative Config](#kong-declarative-config) +- [openapi-2-kong](#openapi-2-kong) + - [Library Usage](#library-usage) + - [Usage Example](#usage-example) + - [Kong Declarative Config](#kong-declarative-config) - [`$._format_version`](#_format_version) - [`$.services`](#services) - [`$.upstreams`](#upstreams) - [`$.services[*].routes`](#servicesroutes) - [`$..tags`](#tags) -- [Kong for Kubernetes](#kong-for-kubernetes) - - [Output Structure](#output-structure) + - [Kong for Kubernetes](#kong-for-kubernetes) + - [Output structure](#output-structure) - [The `Ingress` document](#the-ingress-document) - - [`$.metadata.name`](#metadataname) - - [`$.metadata.annotations`](#metadataannotations) + - [Source spec](#source-spec) + - [Generated config](#generated-config) + - [`$.metadata.name`](#metadataname) + - [`$.metadata.annotations`](#metadataannotations) - [The `KongPlugin` and `KongIngress` resources](#the-kongplugin-and-kongingress-resources) - [Example](#example) - [Defaults](#defaults) - [Plugins](#plugins) - - [Security Plugins](#security-plugins) - - [Generic Plugins](#generic-plugins) - - [Request Validation Plugin](#request-validation-plugin) + - [Security Plugins](#security-plugins) + - [Generic Plugins](#generic-plugins) + - [Request Validator Plugin](#request-validator-plugin) ## Library Usage This module exposes three methods of generating Kong declarative config. ```ts -type ConversionResultType = 'kong-declarative-config' | 'kong-for-kubernetes'; +export type ConversionResultType = 'kong-declarative-config' | 'kong-for-kubernetes'; -generateFromString (spec: string, type: ConversionResultType, tags: Array) => Promise, -generateFromSpec (spec: Object, type: ConversionResultType, tags: Array) => Promise, -generate (filename: string, type: ConversionResultType, tags: Array) => Promise, +export interface DeclarativeConfigResult { + type: 'kong-declarative-config'; + label: string; + documents: DeclarativeConfig[]; + warnings: Warning[]; +} + +export interface KongForKubernetesResult { + type: 'kong-for-kubernetes'; + label: string; + documents: K8sManifest[]; + warnings: Warning[]; +} + +export type ConversionResult = DeclarativeConfigResult | KongForKubernetesResult; + +generateFromSpec( + spec: OpenApi3Spec, + type: ConversionResultType, + tags?: string[], +) => ConversionResult + +generateFromString( + spec: string, + type: ConversionResultType, + tags?: string[], +) => Promise + +generate( + filePath: string, + type: ConversionResultType, + tags?: string[], +) => Promise ``` ### Usage Example @@ -200,6 +232,7 @@ all created resources. ## Kong for Kubernetes ### Output structure + The Kong for Kubernetes config will contain at least one `Ingress` document per server. Depending on where Kong plugins (`x-kong-plugin-`) exist in the specification, several `Ingress` documents may be created, in addition to `KongPlugin` and `KongIngress` documents. @@ -208,6 +241,7 @@ Kong plugins (`x-kong-plugin-`) exist in the specification, several via metadata annotations. ### The `Ingress` document + **At least** one `Ingress` document will be generated for each server. How many are required for each server, will be determined by the presence of `KongPlugin` and `KongIngress` resources that need to be applied to specific paths. @@ -261,6 +295,7 @@ spec: serviceName: insomnia-api-service-1 servicePort: 80 ``` + #### `$.metadata.name` @@ -269,6 +304,7 @@ The `Ingress` document `metadata.name` is derived from sections in the source sp The name is also converted to a lowercase slug. Each of the following specifications generate an `Ingress` document with the following name: + ```yaml apiVersion: extensions/v1beta1 kind: Ingress @@ -316,6 +352,7 @@ Annotations can be used to configure an Ingress document. This configuration app - `konghq.com/override` references single `KongIngress` resource via the resource name ### The `KongPlugin` and `KongIngress` resources + If plugins are found in the OpenAPI spec (see [Plugins](#plugins)), then they are converted to `KongPlugin` resources by the Kong for Kubernetes config generator, before being applied to the `Ingress` document via [metadata annotations](#metadataannotations). @@ -325,12 +362,13 @@ that refers to the operation (`GET`, `POST`, etc). ### Example This example combines all of the information in the previous sections. The input specification contains: + - `x-kubernetes-ingress-metadata` to provide a document name and default annotations - a global plugin at the OpenAPI spec level - two servers, with s1 containing a plugin - two paths with one operation - - no plugins on the first path and operation - - no plugin on the second path, but one on the operation + - no plugins on the first path and operation + - no plugin on the second path, but one on the operation
Input OpenAPI specification @@ -368,9 +406,11 @@ paths: key_in_body: false hide_credentials: true ``` +
The resultant spec creates: + - four unique `Ingress` documents (two servers, two paths, one operation each), - three `KongPlugin` documents (global, server, operation plugin), - one `KongIngress` document (GET operation containing plugin) @@ -487,18 +527,22 @@ spec: serviceName: insomnia-api-service-1 servicePort: 80 ``` + # Defaults While properties for the generated entities will have properties derived from the OpenAPI spec, you may choose to specify defaults for certain properties. You can specify these defaults using the following keys: -* `x-kong-service-defaults` applied to the `root` object -* `x-kong-upstream-defaults` applied to the `root` object -* `x-kong-route-defaults` applied to the `root`, `path` or `operation` object + +- `x-kong-service-defaults` applied to the `root` object +- `x-kong-upstream-defaults` applied to the `root` object +- `x-kong-route-defaults` applied to the `root`, `path` or `operation` object These defaults are only supported by the Declarative Config generator currently. + # Plugins + ## Security Plugins The `security` property can be defined on the top-level `openapi` object as well @@ -515,36 +559,30 @@ by using custom extensions. The custom extensions are Supported types are: - `oauth2` - - NOT YET IMPLEMENTED! - - except for the implicit flow - - implemented using the Kong plugin `openid-connect` - - extended by: `x-kong-security-openid-connect` + - NOT YET IMPLEMENTED! + - except for the implicit flow + - implemented using the Kong plugin `openid-connect` + - extended by: `x-kong-security-openid-connect` - `openIdConnect` - - implemented using the Kong plugin `openid-connect` - - extended by: `x-kong-security-openid-connect` - - properties set from OpenAPI spec: - - `issuer` (from `openIdConnectUrl` property) - - `scopes_required` will get the combined set of scopes from the - extension defaults and the scopes from the Security Requirement - Object + - implemented using the Kong plugin `openid-connect` + - extended by: `x-kong-security-openid-connect` + - properties set from OpenAPI spec: + - `issuer` (from `openIdConnectUrl` property) + - `scopes_required` will get the combined set of scopes from the extension defaults and the scopes from the Security Requirement Object - `apiKey` - - except for the `in` property, since the Kong plugin will by default - look in header and query already. Cookie is not supported. - - implemented using the Kong plugin `key-auth` - - extended by: `x-kong-security-key-auth` - - properties set from OpenAPI spec: - - `key_names` will get the defaults from the extension and then the - `name` from the `securityScheme` object will be added to that list - - requires to add credentials to Kong, which is not supported through - OpenAPI specs. + - except for the `in` property, since the Kong plugin will by default look in header and query already. Cookie is not supported. + - implemented using the Kong plugin `key-auth` + - extended by: `x-kong-security-key-auth` + - properties set from OpenAPI spec: + - `key_names` will get the defaults from the extension and then the `name` from the `securityScheme` object will be added to that list + - requires to add credentials to Kong, which is not supported through OpenAPI specs. - `http` - - only `Basic` scheme is supported - - implemented using the Kong plugin `basic-auth` - - extended by: `x-kong-security-basic-auth` - - properties set from OpenAPI spec: - - none - - requires to add credentials to Kong, which is not supported through - OpenAPI specs. + - only `Basic` scheme is supported + - implemented using the Kong plugin `basic-auth` + - extended by: `x-kong-security-basic-auth` + - properties set from OpenAPI spec: + - none + - requires to add credentials to Kong, which is not supported through OpenAPI specs. ## Generic Plugins @@ -554,12 +592,14 @@ use is `x-kong-plugin-`. The `name` property is not required will get Kong defaults. Currently, plugins are supported on the following OpenAPI objects by each generator: + - Declarative Config: `OpenAPI root`, `path` and `operation` - * plugins on the `root` will be configured on a Kong `service` - * plugins on the `path` or `operation` will be configured on a Kong `route` + - plugins on the `root` will be configured on a Kong `service` + - plugins on the `path` or `operation` will be configured on a Kong `route` - Kong for Kubernetes: `OpenAPI root`, `server`, `path`, `operation` If the _same_ plugin exists in several sections, then the more specific section will take precedence. These sections are: + 1. `operation` (most specific) 1. `path` 1. `server` @@ -591,9 +631,10 @@ x-kong-plugin-key-auth: To enable validation the `request-validator` plugin can be added to the `root`, `path` or `operation` object of the Spec and whichever is more specific will be used. You can either specify the full configuration, or have the missing properties be auto-generated based on the OpenAPI spec. The `request-validator` plugin has three [parameters](https://docs.konghq.com/hub/kong-inc/request-validator/#parameters) which can be generated. These are: -* `config.body_schema` -* `config.parameter_schema` -* `config.allowed_content_types` + +- `config.body_schema` +- `config.parameter_schema` +- `config.allowed_content_types` If any of these are *not* specified in the `x-kong-plugin-request-validator.config` object in the OpenAPI spec, they will be generated if possible, otherwise will be configured to allow all body/parameter/content types. diff --git a/packages/openapi-2-kong/src/common.test.ts b/packages/openapi-2-kong/src/common.test.ts index 0e58470ce..6ac8aace0 100644 --- a/packages/openapi-2-kong/src/common.test.ts +++ b/packages/openapi-2-kong/src/common.test.ts @@ -14,8 +14,9 @@ import { parseUrl, pathVariablesToRegex, } from './common'; -import { getSpec } from './declarative-config/utils'; -import { OA3Operation, OA3Server, xKongName } from './types/openapi3'; +import { getSpec } from './declarative-config/jest/test-helpers'; +import { xKongName } from './types/kong'; +import { OA3Operation, OA3Server } from './types/openapi3'; describe('common', () => { describe('getPaths()', () => { diff --git a/packages/openapi-2-kong/src/common.ts b/packages/openapi-2-kong/src/common.ts index 121c4ccef..eef54f306 100644 --- a/packages/openapi-2-kong/src/common.ts +++ b/packages/openapi-2-kong/src/common.ts @@ -5,16 +5,12 @@ import { OA3PathItem, OA3Server, OA3Operation, - xKongName, } from './types/openapi3'; +import { xKongName } from './types/kong'; -export function getServers(obj: OpenApi3Spec | OA3PathItem) { - return obj.servers || []; -} +export const getServers = (obj: OpenApi3Spec | OA3PathItem) => obj.servers || []; -export function getPaths(obj: OpenApi3Spec) { - return obj.paths || {}; -} +export const getPaths = (obj: OpenApi3Spec) => obj.paths || {}; export function getAllServers(api: OpenApi3Spec) { const servers = getServers(api); diff --git a/packages/openapi-2-kong/src/declarative-config/fixtures/api-with-examples.yaml b/packages/openapi-2-kong/src/declarative-config/fixtures/api-with-examples.yaml index ddec4ff74..afdf40b2e 100644 --- a/packages/openapi-2-kong/src/declarative-config/fixtures/api-with-examples.yaml +++ b/packages/openapi-2-kong/src/declarative-config/fixtures/api-with-examples.yaml @@ -1,4 +1,4 @@ -openapi: "3.0.0" +openapi: '3.0.0' info: title: Simple API overview version: v2 @@ -32,32 +32,36 @@ paths: application/json: examples: foo: - value: { - "versions": [ + value: { - "status": "CURRENT", - "updated": "2011-01-21T11:33:21Z", - "id": "v2.0", - "links": [ - { - "href": "http://127.0.0.1:8774/v2/", - "rel": "self" - } - ] - }, - { - "status": "EXPERIMENTAL", - "updated": "2013-07-23T11:33:21Z", - "id": "v3.0", - "links": [ - { - "href": "http://127.0.0.1:8774/v3/", - "rel": "self" - } - ] + 'versions': + [ + { + 'status': 'CURRENT', + 'updated': '2011-01-21T11:33:21Z', + 'id': 'v2.0', + 'links': + [ + { + 'href': 'http://127.0.0.1:8774/v2/', + 'rel': 'self', + }, + ], + }, + { + 'status': 'EXPERIMENTAL', + 'updated': '2013-07-23T11:33:21Z', + 'id': 'v3.0', + 'links': + [ + { + 'href': 'http://127.0.0.1:8774/v3/', + 'rel': 'self', + }, + ], + }, + ], } - ] - } '300': description: |- 300 response @@ -105,44 +109,48 @@ paths: application/json: examples: foo: - value: { - "version": { - "status": "CURRENT", - "updated": "2011-01-21T11:33:21Z", - "media-types": [ - { - "base": "application/xml", - "type": "application/vnd.openstack.compute+xml;version=2" - }, - { - "base": "application/json", - "type": "application/vnd.openstack.compute+json;version=2" - } - ], - "id": "v2.0", - "links": [ - { - "href": "http://127.0.0.1:8774/v2/", - "rel": "self" - }, - { - "href": "http://docs.openstack.org/api/openstack-compute/2/os-compute-devguide-2.pdf", - "type": "application/pdf", - "rel": "describedby" - }, - { - "href": "http://docs.openstack.org/api/openstack-compute/2/wadl/os-compute-2.wadl", - "type": "application/vnd.sun.wadl+xml", - "rel": "describedby" - }, - { - "href": "http://docs.openstack.org/api/openstack-compute/2/wadl/os-compute-2.wadl", - "type": "application/vnd.sun.wadl+xml", - "rel": "describedby" - } - ] + value: + { + 'version': + { + 'status': 'CURRENT', + 'updated': '2011-01-21T11:33:21Z', + 'media-types': + [ + { + 'base': 'application/xml', + 'type': 'application/vnd.openstack.compute+xml;version=2', + }, + { + 'base': 'application/json', + 'type': 'application/vnd.openstack.compute+json;version=2', + }, + ], + 'id': 'v2.0', + 'links': + [ + { + 'href': 'http://127.0.0.1:8774/v2/', + 'rel': 'self', + }, + { + 'href': 'http://docs.openstack.org/api/openstack-compute/2/os-compute-devguide-2.pdf', + 'type': 'application/pdf', + 'rel': 'describedby', + }, + { + 'href': 'http://docs.openstack.org/api/openstack-compute/2/wadl/os-compute-2.wadl', + 'type': 'application/vnd.sun.wadl+xml', + 'rel': 'describedby', + }, + { + 'href': 'http://docs.openstack.org/api/openstack-compute/2/wadl/os-compute-2.wadl', + 'type': 'application/vnd.sun.wadl+xml', + 'rel': 'describedby', + }, + ], + }, } - } '203': description: |- 203 response @@ -150,36 +158,40 @@ paths: application/json: examples: foo: - value: { - "version": { - "status": "CURRENT", - "updated": "2011-01-21T11:33:21Z", - "media-types": [ - { - "base": "application/xml", - "type": "application/vnd.openstack.compute+xml;version=2" - }, - { - "base": "application/json", - "type": "application/vnd.openstack.compute+json;version=2" - } - ], - "id": "v2.0", - "links": [ - { - "href": "http://23.253.228.211:8774/v2/", - "rel": "self" - }, - { - "href": "http://docs.openstack.org/api/openstack-compute/2/os-compute-devguide-2.pdf", - "type": "application/pdf", - "rel": "describedby" - }, - { - "href": "http://docs.openstack.org/api/openstack-compute/2/wadl/os-compute-2.wadl", - "type": "application/vnd.sun.wadl+xml", - "rel": "describedby" - } - ] + value: + { + 'version': + { + 'status': 'CURRENT', + 'updated': '2011-01-21T11:33:21Z', + 'media-types': + [ + { + 'base': 'application/xml', + 'type': 'application/vnd.openstack.compute+xml;version=2', + }, + { + 'base': 'application/json', + 'type': 'application/vnd.openstack.compute+json;version=2', + }, + ], + 'id': 'v2.0', + 'links': + [ + { + 'href': 'http://23.253.228.211:8774/v2/', + 'rel': 'self', + }, + { + 'href': 'http://docs.openstack.org/api/openstack-compute/2/os-compute-devguide-2.pdf', + 'type': 'application/pdf', + 'rel': 'describedby', + }, + { + 'href': 'http://docs.openstack.org/api/openstack-compute/2/wadl/os-compute-2.wadl', + 'type': 'application/vnd.sun.wadl+xml', + 'rel': 'describedby', + }, + ], + }, } - } diff --git a/packages/openapi-2-kong/src/declarative-config/fixtures/httpbin.expected.json b/packages/openapi-2-kong/src/declarative-config/fixtures/httpbin.expected.json index 25dad891f..ef907ae43 100644 --- a/packages/openapi-2-kong/src/declarative-config/fixtures/httpbin.expected.json +++ b/packages/openapi-2-kong/src/declarative-config/fixtures/httpbin.expected.json @@ -20,23 +20,28 @@ "strip_path": false, "tags": ["OAS3_import", "OAS3file_httpbin.yaml"], "name": "httpbin-basic_auth_user_password-get", - "plugins": [{"config": {"hide_credentials": true}, "name": "basic-auth", - "tags": ["OAS3_import", "OAS3file_httpbin.yaml"]}], - "paths": ["\/basic-auth\/(?[^\\\/\\s]+)\/(?[^\\\/\\s]+)$"] + "plugins": [ + { + "config": { "hide_credentials": true }, + "name": "basic-auth", + "tags": ["OAS3_import", "OAS3file_httpbin.yaml"] + } + ], + "paths": ["/basic-auth/(?[^\\/\\s]+)/(?[^\\/\\s]+)$"] }, { "methods": ["GET"], "strip_path": false, "tags": ["OAS3_import", "OAS3file_httpbin.yaml"], "name": "httpbin-image_format-get", - "paths": ["\/image\/(?[^\\\/\\s]+)$"] + "paths": ["/image/(?[^\\/\\s]+)$"] }, { "methods": ["GET"], "strip_path": false, "tags": ["OAS3_import", "OAS3file_httpbin.yaml"], "name": "httpbin-delay_n-get", - "paths": ["\/delay\/(?[^\\\/\\s]+)$"] + "paths": ["/delay/(?[^\\/\\s]+)$"] }, { "methods": ["GET"], @@ -50,35 +55,35 @@ "strip_path": false, "tags": ["OAS3_import", "OAS3file_httpbin.yaml"], "name": "httpbin-status_statusCode-delete", - "paths": ["\/status\/(?[^\\\/\\s]+)$"] + "paths": ["/status/(?[^\\/\\s]+)$"] }, { "methods": ["PUT"], "strip_path": false, "tags": ["OAS3_import", "OAS3file_httpbin.yaml"], "name": "httpbin-status_statusCode-put", - "paths": ["\/status\/(?[^\\\/\\s]+)$"] + "paths": ["/status/(?[^\\/\\s]+)$"] }, { "methods": ["PATCH"], "strip_path": false, "tags": ["OAS3_import", "OAS3file_httpbin.yaml"], "name": "httpbin-status_statusCode-patch", - "paths": ["\/status\/(?[^\\\/\\s]+)$"] + "paths": ["/status/(?[^\\/\\s]+)$"] }, { "methods": ["POST"], "strip_path": false, "tags": ["OAS3_import", "OAS3file_httpbin.yaml"], "name": "httpbin-status_statusCode-post", - "paths": ["\/status\/(?[^\\\/\\s]+)$"] + "paths": ["/status/(?[^\\/\\s]+)$"] }, { "methods": ["GET"], "strip_path": false, "tags": ["OAS3_import", "OAS3file_httpbin.yaml"], "name": "httpbin-status_statusCode-get", - "paths": ["\/status\/(?[^\\\/\\s]+)$"] + "paths": ["/status/(?[^\\/\\s]+)$"] }, { "methods": ["GET"], @@ -92,9 +97,16 @@ "strip_path": false, "tags": ["OAS3_import", "OAS3file_httpbin.yaml"], "name": "httpbin-hidden_basic_auth_user_password-get", - "plugins": [{"config": {"hide_credentials": true}, "name": "basic-auth", - "tags": ["OAS3_import", "OAS3file_httpbin.yaml"]}], - "paths": ["\/hidden-basic-auth\/(?[^\\\/\\s]+)\/(?[^\\\/\\s]+)$"] + "plugins": [ + { + "config": { "hide_credentials": true }, + "name": "basic-auth", + "tags": ["OAS3_import", "OAS3file_httpbin.yaml"] + } + ], + "paths": [ + "/hidden-basic-auth/(?[^\\/\\s]+)/(?[^\\/\\s]+)$" + ] }, { "methods": ["GET"], @@ -108,7 +120,12 @@ "strip_path": false, "name": "httpbin-cookies-get", "tags": ["OAS3_import", "OAS3file_httpbin.yaml"], - "plugins": [{"name": "correlation-id", "tags": ["OAS3_import", "OAS3file_httpbin.yaml"]}], + "plugins": [ + { + "name": "correlation-id", + "tags": ["OAS3_import", "OAS3file_httpbin.yaml"] + } + ], "paths": ["/cookies$"] }, { @@ -130,7 +147,7 @@ "strip_path": false, "tags": ["OAS3_import", "OAS3file_httpbin.yaml"], "name": "httpbin-cookies_delete-get", - "paths": ["/cookies\/delete$"] + "paths": ["/cookies/delete$"] }, { "methods": ["GET"], @@ -144,7 +161,7 @@ "strip_path": false, "tags": ["OAS3_import", "OAS3file_httpbin.yaml"], "name": "httpbin-parse_machine_timestamp-get", - "paths": ["\/parse\/(?[^\\\/\\s]+)$"] + "paths": ["/parse/(?[^\\/\\s]+)$"] }, { "methods": ["POST"], @@ -188,23 +205,24 @@ ], "version": "draft4" }, - "tags": [ - "OAS3_import", - "OAS3file_httpbin.yaml" - ], + "tags": ["OAS3_import", "OAS3file_httpbin.yaml"], "enabled": true } ], - "paths": ["\/when\/(?[^\\\/\\s]+)$"] + "paths": ["/when/(?[^\\/\\s]+)$"] }, { "methods": ["GET"], "strip_path": false, "tags": ["OAS3_import", "OAS3file_httpbin.yaml"], "name": "httpbin-bearer-get", - "plugins": [{"config": {"hide_credentials": true}, "name": "basic-auth", - "tags": ["OAS3_import", "OAS3file_httpbin.yaml"] - }], + "plugins": [ + { + "config": { "hide_credentials": true }, + "name": "basic-auth", + "tags": ["OAS3_import", "OAS3file_httpbin.yaml"] + } + ], "paths": ["/bearer$"] }, { @@ -233,7 +251,8 @@ { "target": "httpbin.org:443", "tags": ["OAS3_import", "OAS3file_httpbin.yaml"] - }, { + }, + { "target": "eu.httpbin.org:443", "tags": ["OAS3_import", "OAS3file_httpbin.yaml"] } diff --git a/packages/openapi-2-kong/src/declarative-config/fixtures/httpbin.yaml b/packages/openapi-2-kong/src/declarative-config/fixtures/httpbin.yaml index af3f2ac88..71ae5cd10 100644 --- a/packages/openapi-2-kong/src/declarative-config/fixtures/httpbin.yaml +++ b/packages/openapi-2-kong/src/declarative-config/fixtures/httpbin.yaml @@ -111,7 +111,7 @@ paths: parameters: - $ref: '#/components/parameters/freeFormQuery' responses: - '200': # Change to 'default' ??? + '200': # Change to 'default' ??? description: OK content: application/json: @@ -236,7 +236,7 @@ paths: value: foo: bar # multipart/form-data: {} # TODO - '*/*': # is this valid? + '*/*': # is this valid? schema: {} responses: '200': @@ -264,7 +264,6 @@ paths: # get: {} # post: {} - /ip: get: summary: Returns Origin IP. @@ -284,7 +283,7 @@ paths: example: 10.100.10.10 required: - origin - examples: # Content examples override schema-level examples + examples: # Content examples override schema-level examples oneIp: description: Example of a single IP value: @@ -377,7 +376,8 @@ paths: '200': description: OK content: - application/json: {} + application/json: + {} # schema: # type: object @@ -448,7 +448,6 @@ paths: '404': description: Unauthorized - /status/{statusCode}: summary: Returns the specified HTTP status code, or a random status code if more than one are given parameters: @@ -617,7 +616,6 @@ paths: 200: description: OK - ################################# # Reusable things ################################# @@ -670,7 +668,8 @@ components: - $ref: '#/components/schemas/CommonResponse' - type: object properties: - data: {} # Always a plain text string??? + data: + {} # Always a plain text string??? # ??? # oneOf: # - type: string @@ -808,7 +807,6 @@ components: Note that the parameters will be actually sent as `?param1=value1¶m2=value2&...` - responses: 200BasicAuth: description: OK @@ -830,8 +828,8 @@ components: schema: type: string format: binary - image/svg+xml: {} # string? object? - image/svg: {} # string? object? + image/svg+xml: {} # string? object? + image/svg: {} # string? object? TimestampResponse: description: OK @@ -864,4 +862,4 @@ components: slang_time": 4 months ago iso8601: '2017-01-23T14:59:10.846440Z' rfc2822: Mon, 23 Jan 2017 14:59:10 GMT - rfc3339: '2017-01-23T14:59:10.84Z' \ No newline at end of file + rfc3339: '2017-01-23T14:59:10.84Z' diff --git a/packages/openapi-2-kong/src/declarative-config/fixtures/link-example.expected.json b/packages/openapi-2-kong/src/declarative-config/fixtures/link-example.expected.json index 10c27d7f2..0beb3a61f 100644 --- a/packages/openapi-2-kong/src/declarative-config/fixtures/link-example.expected.json +++ b/packages/openapi-2-kong/src/declarative-config/fixtures/link-example.expected.json @@ -13,28 +13,32 @@ "strip_path": false, "tags": ["OAS3_import", "OAS3file_link-example.yaml"], "name": "Link_Example-getUserByName", - "paths": ["\/2.0\/users\/(?[^\\\/\\s]+)$"] + "paths": ["/2.0/users/(?[^\\/\\s]+)$"] }, { "methods": ["GET"], "strip_path": false, "tags": ["OAS3_import", "OAS3file_link-example.yaml"], "name": "Link_Example-getRepositoriesByOwner", - "paths": ["\/2.0\/repositories\/(?[^\\\/\\s]+)$"] + "paths": ["/2.0/repositories/(?[^\\/\\s]+)$"] }, { "methods": ["GET"], "strip_path": false, "tags": ["OAS3_import", "OAS3file_link-example.yaml"], "name": "Link_Example-getRepository", - "paths": ["\/2.0\/repositories\/(?[^\\\/\\s]+)\/(?[^\\\/\\s]+)$"] + "paths": [ + "/2.0/repositories/(?[^\\/\\s]+)/(?[^\\/\\s]+)$" + ] }, { "methods": ["GET"], "strip_path": false, "tags": ["OAS3_import", "OAS3file_link-example.yaml"], "name": "Link_Example-getPullRequestsByRepository", - "paths": ["\/2.0\/repositories\/(?[^\\\/\\s]+)\/(?[^\\\/\\s]+)\/pullrequests$"] + "paths": [ + "/2.0/repositories/(?[^\\/\\s]+)/(?[^\\/\\s]+)/pullrequests$" + ] }, { "methods": ["GET"], @@ -42,7 +46,7 @@ "tags": ["OAS3_import", "OAS3file_link-example.yaml"], "name": "Link_Example-getPullRequestsById", "paths": [ - "\/2.0\/repositories\/(?[^\\\/\\s]+)\/(?[^\\\/\\s]+)\/pullrequests\/(?[^\\\/\\s]+)$" + "/2.0/repositories/(?[^\\/\\s]+)/(?[^\\/\\s]+)/pullrequests/(?[^\\/\\s]+)$" ] }, { @@ -51,7 +55,7 @@ "tags": ["OAS3_import", "OAS3file_link-example.yaml"], "name": "Link_Example-mergePullRequest", "paths": [ - "\/2.0\/repositories\/(?[^\\\/\\s]+)\/(?[^\\\/\\s]+)\/pullrequests\/(?[^\\\/\\s]+)\/merge$" + "/2.0/repositories/(?[^\\/\\s]+)/(?[^\\/\\s]+)/pullrequests/(?[^\\/\\s]+)/merge$" ] } ], diff --git a/packages/openapi-2-kong/src/declarative-config/fixtures/link-example.yaml b/packages/openapi-2-kong/src/declarative-config/fixtures/link-example.yaml index 7c422c348..eaa23f1f1 100644 --- a/packages/openapi-2-kong/src/declarative-config/fixtures/link-example.yaml +++ b/packages/openapi-2-kong/src/declarative-config/fixtures/link-example.yaml @@ -9,11 +9,11 @@ paths: get: operationId: getUserByName parameters: - - name: username - in: path - required: true - schema: - type: string + - name: username + in: path + required: true + schema: + type: string responses: '200': description: The User @@ -63,9 +63,9 @@ paths: '200': description: The repository content: - application/json: - schema: - $ref: '#/components/schemas/repository' + application/json: + schema: + $ref: '#/components/schemas/repository' links: repositoryPullRequests: $ref: '#/components/links/RepositoryPullRequests' @@ -73,24 +73,24 @@ paths: get: operationId: getPullRequestsByRepository parameters: - - name: username - in: path - required: true - schema: - type: string - - name: slug - in: path - required: true - schema: - type: string - - name: state - in: query - schema: - type: string - enum: - - open - - merged - - declined + - name: username + in: path + required: true + schema: + type: string + - name: slug + in: path + required: true + schema: + type: string + - name: state + in: query + schema: + type: string + enum: + - open + - merged + - declined responses: '200': description: an array of pull request objects @@ -104,21 +104,21 @@ paths: get: operationId: getPullRequestsById parameters: - - name: username - in: path - required: true - schema: - type: string - - name: slug - in: path - required: true - schema: - type: string - - name: pid - in: path - required: true - schema: - type: string + - name: username + in: path + required: true + schema: + type: string + - name: slug + in: path + required: true + schema: + type: string + - name: pid + in: path + required: true + schema: + type: string responses: '200': description: a pull request object @@ -133,21 +133,21 @@ paths: post: operationId: mergePullRequest parameters: - - name: username - in: path - required: true - schema: - type: string - - name: slug - in: path - required: true - schema: - type: string - - name: pid - in: path - required: true - schema: - type: string + - name: username + in: path + required: true + schema: + type: string + - name: slug + in: path + required: true + schema: + type: string + - name: pid + in: path + required: true + schema: + type: string responses: '204': description: the PR was successfully merged @@ -168,8 +168,8 @@ components: # returns '#/components/schemas/pullrequest' operationId: getPullRequestsByRepository parameters: - username: $response.body#/owner/username - slug: $response.body#/slug + username: $response.body#/owner/username + slug: $response.body#/slug PullRequestMerge: # executes /2.0/repositories/{username}/{slug}/pullrequests/{pid}/merge operationId: mergePullRequest diff --git a/packages/openapi-2-kong/src/declarative-config/fixtures/no-targets-example.yaml b/packages/openapi-2-kong/src/declarative-config/fixtures/no-targets-example.yaml index b95733fef..0a13930b6 100644 --- a/packages/openapi-2-kong/src/declarative-config/fixtures/no-targets-example.yaml +++ b/packages/openapi-2-kong/src/declarative-config/fixtures/no-targets-example.yaml @@ -1,4 +1,4 @@ -openapi: "3.0.0" +openapi: '3.0.0' info: title: Simple API overview version: v2 diff --git a/packages/openapi-2-kong/src/declarative-config/fixtures/petstore-expanded.expected.json b/packages/openapi-2-kong/src/declarative-config/fixtures/petstore-expanded.expected.json index 3d121df00..f2df77332 100644 --- a/packages/openapi-2-kong/src/declarative-config/fixtures/petstore-expanded.expected.json +++ b/packages/openapi-2-kong/src/declarative-config/fixtures/petstore-expanded.expected.json @@ -27,14 +27,14 @@ "strip_path": false, "tags": ["OAS3_import", "OAS3file_petstore-expanded.yaml"], "name": "Swagger_Petstore-find_pet_by_id", - "paths": ["\/pets\/(?[^\\\/\\s]+)$"] + "paths": ["/pets/(?[^\\/\\s]+)$"] }, { "methods": ["DELETE"], "strip_path": false, "tags": ["OAS3_import", "OAS3file_petstore-expanded.yaml"], "name": "Swagger_Petstore-deletePet", - "paths": ["\/pets\/(?[^\\\/\\s]+)$"] + "paths": ["/pets/(?[^\\/\\s]+)$"] } ], "tags": ["OAS3_import", "OAS3file_petstore-expanded.yaml"] diff --git a/packages/openapi-2-kong/src/declarative-config/fixtures/petstore-expanded.yaml b/packages/openapi-2-kong/src/declarative-config/fixtures/petstore-expanded.yaml index ff02ce359..0111f1cf0 100644 --- a/packages/openapi-2-kong/src/declarative-config/fixtures/petstore-expanded.yaml +++ b/packages/openapi-2-kong/src/declarative-config/fixtures/petstore-expanded.yaml @@ -1,4 +1,4 @@ -openapi: "3.0.0" +openapi: '3.0.0' info: version: 1.0.0 title: Swagger Petstore @@ -128,7 +128,7 @@ components: allOf: - $ref: '#/components/schemas/NewPet' - required: - - id + - id properties: id: type: integer @@ -136,7 +136,7 @@ components: NewPet: required: - - name + - name properties: name: type: string diff --git a/packages/openapi-2-kong/src/declarative-config/fixtures/petstore.expected.json b/packages/openapi-2-kong/src/declarative-config/fixtures/petstore.expected.json index debbc3d01..823c4a2e6 100644 --- a/packages/openapi-2-kong/src/declarative-config/fixtures/petstore.expected.json +++ b/packages/openapi-2-kong/src/declarative-config/fixtures/petstore.expected.json @@ -27,7 +27,7 @@ "strip_path": false, "tags": ["OAS3_import", "OAS3file_petstore.yaml"], "name": "Swagger_Petstore-showPetById", - "paths": ["\/pets\/(?[^\\\/\\s]+)$"] + "paths": ["/pets/(?[^\\/\\s]+)$"] } ], "tags": ["OAS3_import", "OAS3file_petstore.yaml"] diff --git a/packages/openapi-2-kong/src/declarative-config/fixtures/petstore.yaml b/packages/openapi-2-kong/src/declarative-config/fixtures/petstore.yaml index 09941de97..35f2a7709 100644 --- a/packages/openapi-2-kong/src/declarative-config/fixtures/petstore.yaml +++ b/packages/openapi-2-kong/src/declarative-config/fixtures/petstore.yaml @@ -1,4 +1,4 @@ -openapi: "3.0.0" +openapi: '3.0.0' info: version: 1.0.0 title: Swagger Petstore @@ -30,15 +30,15 @@ paths: schema: type: string content: - application/json: + application/json: schema: - $ref: "#/components/schemas/Pets" + $ref: '#/components/schemas/Pets' default: description: unexpected error content: application/json: schema: - $ref: "#/components/schemas/Error" + $ref: '#/components/schemas/Error' post: summary: Create a pet operationId: createPets @@ -52,7 +52,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/Error" + $ref: '#/components/schemas/Error' /pets/{petId}: get: summary: Info for a specific pet @@ -72,13 +72,13 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/Pets" + $ref: '#/components/schemas/Pets' default: description: unexpected error content: application/json: schema: - $ref: "#/components/schemas/Error" + $ref: '#/components/schemas/Error' components: schemas: Pet: @@ -96,7 +96,7 @@ components: Pets: type: array items: - $ref: "#/components/schemas/Pet" + $ref: '#/components/schemas/Pet' Error: required: - code 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 82a2f7389..efdab41cc 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,22 +13,15 @@ "body_schema": "{}", "version": "draft4" }, - "tags": [ - "OAS3_import", - "OAS3file_request-validator-plugin.yaml" - ], + "tags": ["OAS3_import", "OAS3file_request-validator-plugin.yaml"], "name": "request-validator" } ], "routes": [ { - "methods": [ - "POST" - ], + "methods": ["POST"], "name": "Example-body-post", - "paths": [ - "/body$" - ], + "paths": ["/body$"], "plugins": [ { "config": { @@ -39,57 +32,35 @@ "body_schema": "{\"type\":\"object\",\"properties\":{\"id\":{\"type\":\"integer\"},\"name\":{\"type\":\"string\"}}}", "version": "draft4" }, - "tags": [ - "OAS3_import", - "OAS3file_request-validator-plugin.yaml" - ], + "tags": ["OAS3_import", "OAS3file_request-validator-plugin.yaml"], "name": "request-validator" } ], "strip_path": false, - "tags": [ - "OAS3_import", - "OAS3file_request-validator-plugin.yaml" - ] + "tags": ["OAS3_import", "OAS3file_request-validator-plugin.yaml"] }, { - "methods": [ - "GET" - ], + "methods": ["GET"], "name": "Example-global-get", - "paths": [ - "/global$" - ], + "paths": ["/global$"], "plugins": [ { "config": { - "allowed_content_types": [ - "application/json" - ], + "allowed_content_types": ["application/json"], "body_schema": "{\"type\":\"object\",\"properties\":{\"id\":{\"type\":\"integer\"},\"name\":{\"type\":\"string\"}}}", "version": "draft4" }, - "tags": [ - "OAS3_import", - "OAS3file_request-validator-plugin.yaml" - ], + "tags": ["OAS3_import", "OAS3file_request-validator-plugin.yaml"], "name": "request-validator" } ], "strip_path": false, - "tags": [ - "OAS3_import", - "OAS3file_request-validator-plugin.yaml" - ] + "tags": ["OAS3_import", "OAS3file_request-validator-plugin.yaml"] }, { - "methods": [ - "GET" - ], + "methods": ["GET"], "name": "Example-params_pathid-get", - "paths": [ - "/params/(?[^\\/\\s]+)/$" - ], + "paths": ["/params/(?[^\\/\\s]+)/$"], "plugins": [ { "config": { @@ -130,40 +101,25 @@ "version": "draft4" }, "enabled": true, - "tags": [ - "OAS3_import", - "OAS3file_request-validator-plugin.yaml" - ], + "tags": ["OAS3_import", "OAS3file_request-validator-plugin.yaml"], "name": "request-validator" } ], "strip_path": false, - "tags": [ - "OAS3_import", - "OAS3file_request-validator-plugin.yaml" - ] + "tags": ["OAS3_import", "OAS3file_request-validator-plugin.yaml"] } ], - "tags": [ - "OAS3_import", - "OAS3file_request-validator-plugin.yaml" - ] + "tags": ["OAS3_import", "OAS3file_request-validator-plugin.yaml"] } ], "upstreams": [ { "name": "Example", - "tags": [ - "OAS3_import", - "OAS3file_request-validator-plugin.yaml" - ], + "tags": ["OAS3_import", "OAS3file_request-validator-plugin.yaml"], "targets": [ { "target": "backend.com:80", - "tags": [ - "OAS3_import", - "OAS3file_request-validator-plugin.yaml" - ] + "tags": ["OAS3_import", "OAS3file_request-validator-plugin.yaml"] } ] } diff --git a/packages/openapi-2-kong/src/declarative-config/fixtures/security.expected.json b/packages/openapi-2-kong/src/declarative-config/fixtures/security.expected.json index 6fa67c30d..24627c64a 100644 --- a/packages/openapi-2-kong/src/declarative-config/fixtures/security.expected.json +++ b/packages/openapi-2-kong/src/declarative-config/fixtures/security.expected.json @@ -14,7 +14,9 @@ "tags": ["OAS3_import", "OAS3file_security.yaml"], "name": "Security_Example-only_oath2-get", "plugins": [ - {"config": {"auth_methods": ["client_credentials"]}, "name": "openid-connect", + { + "config": { "auth_methods": ["client_credentials"] }, + "name": "openid-connect", "tags": ["OAS3_import", "OAS3file_security.yaml"] } ], @@ -25,8 +27,13 @@ "strip_path": false, "tags": ["OAS3_import", "OAS3file_security.yaml"], "name": "Security_Example-only_key-get", - "plugins": [{"config": {"key_names": ["api_key_by_me"]}, "name": "key-auth", - "tags": ["OAS3_import", "OAS3file_security.yaml"]}], + "plugins": [ + { + "config": { "key_names": ["api_key_by_me"] }, + "name": "key-auth", + "tags": ["OAS3_import", "OAS3file_security.yaml"] + } + ], "paths": ["/only/key$"] }, { @@ -35,8 +42,11 @@ "tags": ["OAS3_import", "OAS3file_security.yaml"], "name": "Security_Example-and_based_auth-get", "plugins": [ - {"config": {"auth_methods": ["client_credentials"]}, "name": "openid-connect", - "tags": ["OAS3_import", "OAS3file_security.yaml"]} + { + "config": { "auth_methods": ["client_credentials"] }, + "name": "openid-connect", + "tags": ["OAS3_import", "OAS3file_security.yaml"] + } ], "paths": ["/and/based/auth$"] }, @@ -45,8 +55,13 @@ "strip_path": false, "tags": ["OAS3_import", "OAS3file_security.yaml"], "name": "Security_Example-or_based_auth-get", - "plugins": [{"config": {"key_names": ["api_key_by_me"]}, "name": "key-auth", - "tags": ["OAS3_import", "OAS3file_security.yaml"]}], + "plugins": [ + { + "config": { "key_names": ["api_key_by_me"] }, + "name": "key-auth", + "tags": ["OAS3_import", "OAS3file_security.yaml"] + } + ], "paths": ["/or/based/auth$"] }, { @@ -58,7 +73,7 @@ { "config": { "scopes_required": ["write:pets"], - "issuer": "http:\/\/example.org\/oid-discovery" + "issuer": "http://example.org/oid-discovery" }, "name": "openid-connect", "tags": ["OAS3_import", "OAS3file_security.yaml"] @@ -71,8 +86,13 @@ "strip_path": false, "tags": ["OAS3_import", "OAS3file_security.yaml"], "name": "Security_Example-only_basic-get", - "plugins": [{"name": "basic-auth", - "tags": ["OAS3_import", "OAS3file_security.yaml", "should match"], "config": {"hide_credentials": true}}], + "plugins": [ + { + "name": "basic-auth", + "tags": ["OAS3_import", "OAS3file_security.yaml", "should match"], + "config": { "hide_credentials": true } + } + ], "paths": ["/only/basic$"] } ], diff --git a/packages/openapi-2-kong/src/declarative-config/fixtures/security.yaml b/packages/openapi-2-kong/src/declarative-config/fixtures/security.yaml index 0d8bfb789..029372eee 100644 --- a/packages/openapi-2-kong/src/declarative-config/fixtures/security.yaml +++ b/packages/openapi-2-kong/src/declarative-config/fixtures/security.yaml @@ -11,7 +11,7 @@ paths: /only/basic: get: security: - - really_basic: [] + - really_basic: [] responses: '200': description: Saying hello @@ -19,7 +19,7 @@ paths: /only/key: get: security: - - my_api_key: [] + - my_api_key: [] responses: '200': description: Saying hello @@ -27,8 +27,8 @@ paths: /only/oath2: get: security: - - petstore_oauth2: - - read:pets + - petstore_oauth2: + - read:pets responses: '200': description: Saying hello @@ -36,8 +36,8 @@ paths: /only/oidc: get: security: - - petstore_openid: - - write:pets + - petstore_openid: + - write:pets responses: '200': description: Saying hello @@ -45,9 +45,9 @@ paths: /and/based/auth: get: security: - - petstore_oauth2: - - read:pets - #my_api_key: [] + - petstore_oauth2: + - read:pets + #my_api_key: [] responses: '200': description: Saying hello @@ -55,7 +55,7 @@ paths: /or/based/auth: get: security: - - my_api_key: [] + - my_api_key: [] #- really_basic: [] responses: '200': diff --git a/packages/openapi-2-kong/src/declarative-config/fixtures/uspto.expected.json b/packages/openapi-2-kong/src/declarative-config/fixtures/uspto.expected.json index 503b37ba0..313be82db 100644 --- a/packages/openapi-2-kong/src/declarative-config/fixtures/uspto.expected.json +++ b/packages/openapi-2-kong/src/declarative-config/fixtures/uspto.expected.json @@ -20,14 +20,14 @@ "strip_path": false, "tags": ["OAS3_import", "OAS3file_uspto.yaml"], "name": "USPTO_Data_Set_API-list_searchable_fields", - "paths": ["\/(?[^\\\/\\s]+)\/(?[^\\\/\\s]+)\/fields$"] + "paths": ["/(?[^\\/\\s]+)/(?[^\\/\\s]+)/fields$"] }, { "methods": ["POST"], "strip_path": false, "tags": ["OAS3_import", "OAS3file_uspto.yaml"], "name": "USPTO_Data_Set_API-perform_search", - "paths": ["\/(?[^\\\/\\s]+)\/(?[^\\\/\\s]+)\/records$"] + "paths": ["/(?[^\\/\\s]+)/(?[^\\/\\s]+)/records$"] } ], "tags": ["OAS3_import", "OAS3file_uspto.yaml"] diff --git a/packages/openapi-2-kong/src/declarative-config/fixtures/uspto.yaml b/packages/openapi-2-kong/src/declarative-config/fixtures/uspto.yaml index 104a7f091..32836da9b 100644 --- a/packages/openapi-2-kong/src/declarative-config/fixtures/uspto.yaml +++ b/packages/openapi-2-kong/src/declarative-config/fixtures/uspto.yaml @@ -46,21 +46,22 @@ paths: $ref: '#/components/schemas/dataSetList' example: { - "total": 2, - "apis": [ - { - "apiKey": "oa_citations", - "apiVersionNumber": "v1", - "apiUrl": "https://developer.uspto.gov/ds-api/oa_citations/v1/fields", - "apiDocumentationUrl": "https://developer.uspto.gov/ds-api-docs/index.html?url=https://developer.uspto.gov/ds-api/swagger/docs/oa_citations.json" - }, - { - "apiKey": "cancer_moonshot", - "apiVersionNumber": "v1", - "apiUrl": "https://developer.uspto.gov/ds-api/cancer_moonshot/v1/fields", - "apiDocumentationUrl": "https://developer.uspto.gov/ds-api-docs/index.html?url=https://developer.uspto.gov/ds-api/swagger/docs/cancer_moonshot.json" - } - ] + 'total': 2, + 'apis': + [ + { + 'apiKey': 'oa_citations', + 'apiVersionNumber': 'v1', + 'apiUrl': 'https://developer.uspto.gov/ds-api/oa_citations/v1/fields', + 'apiDocumentationUrl': 'https://developer.uspto.gov/ds-api-docs/index.html?url=https://developer.uspto.gov/ds-api/swagger/docs/oa_citations.json', + }, + { + 'apiKey': 'cancer_moonshot', + 'apiVersionNumber': 'v1', + 'apiUrl': 'https://developer.uspto.gov/ds-api/cancer_moonshot/v1/fields', + 'apiDocumentationUrl': 'https://developer.uspto.gov/ds-api-docs/index.html?url=https://developer.uspto.gov/ds-api/swagger/docs/cancer_moonshot.json', + }, + ], } /{dataset}/{version}/fields: get: @@ -80,14 +81,14 @@ paths: in: path description: 'Name of the dataset.' required: true - example: "oa_citations" + example: 'oa_citations' schema: type: string - name: version in: path description: Version of the dataset. required: true - example: "v1" + example: 'v1' schema: type: string responses: diff --git a/packages/openapi-2-kong/src/declarative-config/generate.ts b/packages/openapi-2-kong/src/declarative-config/generate.ts index bdf2b07dd..464b12fa5 100644 --- a/packages/openapi-2-kong/src/declarative-config/generate.ts +++ b/packages/openapi-2-kong/src/declarative-config/generate.ts @@ -21,9 +21,8 @@ export function generateDeclarativeConfigFromSpec( warnings: [], }; - // This remover any circular references or weirdness that might result - // from the JS objects used. - // SEE: https://github.com/Kong/studio/issues/93 + // This removes any circular references or weirdness that might result from the JS objects used. + // see: https://github.com/Kong/studio/issues/93 const result: DeclarativeConfigResult = JSON.parse(JSON.stringify(declarativeConfigResult)); return result; } catch (err) { diff --git a/packages/openapi-2-kong/src/declarative-config/jest/test-helpers.ts b/packages/openapi-2-kong/src/declarative-config/jest/test-helpers.ts new file mode 100644 index 000000000..10f25f746 --- /dev/null +++ b/packages/openapi-2-kong/src/declarative-config/jest/test-helpers.ts @@ -0,0 +1,90 @@ +import { DCPlugin, K8sKongPlugin, K8sKongPluginBase, OpenApi3Spec } from '../../types'; +import { PluginBase, XKongPlugin } from '../../types/kong'; + +export const tags = ['Tag']; + +/** used only for testing */ +export interface DummyPlugin extends PluginBase<'dummy'> { + config: { + foo: 'bar'; + }; +} +export type XKongPluginDummy = XKongPlugin; +export const pluginDummy: XKongPluginDummy = { + 'x-kong-plugin-dummy': { + name: 'dummy', + config: { + foo: 'bar', + }, + }, +}; + +export const dummyName = (suffix: string) => `add-dummy-${suffix}`; + +export const dummyPluginDoc = (suffix: string): K8sKongPluginBase => ({ + apiVersion: 'configuration.konghq.com/v1', + config: { + foo: 'bar', + }, + kind: 'KongPlugin', + metadata: { + name: dummyName(suffix), + }, + plugin: 'dummy', +}); + +/** + * This simulates what a user would do when creating a custom plugin. + * + * In the user's case they would, in practice, use module augmentation to extend DCPlugin, however a simple union achieves the same goal, here. + */ +export type UserDCPlugin = DCPlugin | DummyPlugin; + +/** + * This simulates what a user would do when creating a custom plugin. + * + * In the user's case they would, in practice, use module augmentation to extend K8sKongPlugin, however a simple union achieves the same goal, here. + */ +export type UserK8sPlugin = K8sKongPlugin | K8sKongPluginBase + +/** + * This simulates what a user would do when creating a custom plugin. + * + * In the user's case they would, in practice, use module augmentation to extend K8sKongPlugin, however a simple union achieves the same goal, here. + */ +export type UserXKongPlugin = XKongPlugin | XKongPlugin + +/** This function is written in such a way as to allow mutations in tests but without affecting other tests. */ +export const getSpec = (overrides: Partial = {}): OpenApi3Spec => + JSON.parse( + JSON.stringify({ + openapi: '3.0', + info: { + version: '1.0', + title: 'My API', + }, + servers: [ + { + url: 'https://server1.com/path', + }, + ], + paths: { + '/cats': { + 'x-kong-name': 'Cat stuff', + summary: 'summary is ignored', + post: {}, + }, + '/dogs': { + summary: 'Dog stuff', + get: {}, + post: { + summary: 'Ignored summary', + }, + }, + '/birds/{id}': { + get: {}, + }, + }, + ...overrides, + }), + ); 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 3f70897ee..f138a49d5 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,5 @@ -import { OA3PathItem, OpenApi3Spec, xKongName } from '../types/openapi3'; +import { xKongName } from '../types/kong'; +import { OA3PathItem, OpenApi3Spec } from '../types/openapi3'; import { generateRouteName } from './services'; const api: OpenApi3Spec = { 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 03981212e..54decd2bd 100644 --- a/packages/openapi-2-kong/src/declarative-config/plugins.test.ts +++ b/packages/openapi-2-kong/src/declarative-config/plugins.test.ts @@ -1,26 +1,21 @@ -import { generateGlobalPlugins, generateRequestValidatorPlugin } from './plugins'; -import { OA3Operation, OA3Parameter, xKongPluginKeyAuth, xKongPluginRequestTermination, xKongPluginRequestValidator } from '../types/openapi3'; -import { DCPlugin, DCPluginConfig } from '../types/declarative-config'; -import { getSpec } from './utils'; - -const tags = ['Tag']; +import { generateGlobalPlugins, generateRequestValidatorPlugin, ALLOW_ALL_SCHEMA } from './plugins'; +import { OA3Operation, OA3Parameter } from '../types/openapi3'; +import { ParameterSchema, RequestTerminationPlugin, RequestValidatorPlugin, xKongPluginKeyAuth, xKongPluginRequestTermination, xKongPluginRequestValidator } from '../types/kong'; +import { getSpec, pluginDummy, UserDCPlugin, tags } from './jest/test-helpers'; describe('plugins', () => { describe('generateGlobalPlugins()', () => { it('generates plugin given a spec with a plugin attached', async () => { const api = getSpec({ [xKongPluginRequestValidator]: { + name: 'request-validator', enabled: false, config: { + body_schema: ALLOW_ALL_SCHEMA, verbose_response: true, }, }, - // @ts-expect-error -- TSCONVERSION needs work for generic for XKongPluginUnknown - 'x-kong-plugin-abcd': { - config: { - some_config: ['something'], - }, - }, + ...pluginDummy, [xKongPluginKeyAuth]: { name: 'key-auth', config: { @@ -31,12 +26,12 @@ describe('plugins', () => { const result = generateGlobalPlugins(api, tags); - expect(result.plugins).toEqual([ + expect(result.plugins).toEqual([ { - name: 'abcd', + name: 'dummy', tags, config: { - some_config: ['something'], + foo: 'bar', }, }, { @@ -48,7 +43,7 @@ describe('plugins', () => { }, { config: { - body_schema: '{}', + body_schema: ALLOW_ALL_SCHEMA, verbose_response: true, version: 'draft4', }, @@ -58,7 +53,9 @@ describe('plugins', () => { }, ]); expect(result.requestValidatorPlugin).toEqual({ + name: 'request-validator', config: { + body_schema: ALLOW_ALL_SCHEMA, verbose_response: true, }, enabled: false, @@ -71,6 +68,7 @@ describe('plugins', () => { name: 'request-termination', mad: 'max', config: { + // @ts-expect-error this is intentionally passing in an extra property max: 'is mad', status_code: 403, message: 'So long and thanks for all the fish!', @@ -80,12 +78,13 @@ describe('plugins', () => { const result = generateGlobalPlugins(spec, tags); - expect(result.plugins).toEqual([ + expect(result.plugins).toEqual([ { name: 'request-termination', mad: 'max', tags, config: { + // @ts-expect-error this is intentionally passing in an extra property max: 'is mad', status_code: 403, message: 'So long and thanks for all the fish!', @@ -96,19 +95,18 @@ describe('plugins', () => { }); describe('generateRequestValidatorPlugin()', () => { - const parameterSchema = [ - { - schema: '{"anyOf":[{"type":"string"}]}', - style: 'form', - in: 'path', - name: 'human_timestamp', - required: true, - explode: false, - }, - ]; + const parameterSchema: ParameterSchema = { + explode: false, + in: 'path', + name: 'human_timestamp', + required: true, + schema: '{"anyOf":[{"type":"string"}]}', + style: 'form', + }; it('should retain config properties', () => { - const plugin = { + const plugin: RequestValidatorPlugin = { + name: 'request-validator', enabled: true, config: { parameter_schema: [parameterSchema], @@ -118,11 +116,12 @@ describe('plugins', () => { }, }; const generated = generateRequestValidatorPlugin({ plugin, tags }); - expect(generated).toStrictEqual({ + expect(generated).toStrictEqual({ name: 'request-validator', enabled: plugin.enabled, tags, config: { + body_schema: ALLOW_ALL_SCHEMA, version: 'draft4', ...plugin.config, }, @@ -130,7 +129,8 @@ describe('plugins', () => { }); it('should not add config properties if they are not defined', () => { - const plugin = { + const plugin: RequestValidatorPlugin = { + name: 'request-validator', enabled: true, config: { parameter_schema: [parameterSchema], @@ -141,11 +141,12 @@ describe('plugins', () => { }, }; const generated = generateRequestValidatorPlugin({ plugin, tags }); - expect(generated).toStrictEqual({ + expect(generated).toStrictEqual({ name: 'request-validator', enabled: plugin.enabled, tags, config: { + body_schema: ALLOW_ALL_SCHEMA, version: 'draft4', ...plugin.config, }, @@ -154,11 +155,19 @@ describe('plugins', () => { describe('parameter_schema', () => { it('should not add parameter_schema if no parameters present', () => { - const plugin = { - enabled: true, - config: {}, + const plugin: RequestValidatorPlugin = { + name: 'request-validator', + config: { + body_schema: ALLOW_ALL_SCHEMA, + }, }; + const generated1 = generateRequestValidatorPlugin({ plugin, tags }); + expect(generated1.config).toStrictEqual({ + version: 'draft4', + body_schema: ALLOW_ALL_SCHEMA, + }); + const generated2 = generateRequestValidatorPlugin({ plugin, operation: { @@ -166,20 +175,13 @@ describe('plugins', () => { }, tags, }); - expect(generated1.config).toStrictEqual({ + expect(generated2.config).toStrictEqual({ version: 'draft4', - body_schema: '{}', - }); - expect(generated2.config).toStrictEqual({ - version: 'draft4', - body_schema: '{}', + body_schema: ALLOW_ALL_SCHEMA, }); }); it('should convert operation parameters to parameter_schema', () => { - const plugin = { - config: {}, - }; const param: OA3Parameter = { in: 'query', explode: true, @@ -197,24 +199,23 @@ describe('plugins', () => { const operation: OA3Operation = { parameters: [param], }; - const generated = generateRequestValidatorPlugin({ plugin, tags, operation }); - expect(generated.config).toStrictEqual({ + const generated = generateRequestValidatorPlugin({ tags, operation }); + expect(generated.config).toStrictEqual({ version: 'draft4', parameter_schema: [ { - schema: '{"anyOf":[{"type":"string"}]}', - style: param.style, in: param.in, name: param.name, + required: Boolean(param.required), + style: param.style, explode: param.explode, - required: param.required, + schema: '{"anyOf":[{"type":"string"}]}', }, ], }); }); it('should return default if operation parameter schema not defined on any parameters', () => { - const plugin = {}; const operation: OA3Operation = { parameters: [ { @@ -223,8 +224,8 @@ describe('plugins', () => { }, ], }; - const generated = generateRequestValidatorPlugin({ plugin, tags, operation }); - expect(generated.config).toStrictEqual({ + const generated = generateRequestValidatorPlugin({ tags, operation }); + expect(generated.config).toStrictEqual({ version: 'draft4', parameter_schema: [ { @@ -264,23 +265,23 @@ describe('plugins', () => { paramWithoutSchema, ], }; - const generated = generateRequestValidatorPlugin({ plugin: {}, tags, operation }); - expect(generated.config).toStrictEqual({ + const generated = generateRequestValidatorPlugin({ tags, operation }); + expect(generated.config).toStrictEqual({ version: 'draft4', parameter_schema: [ { schema: '{"anyOf":[{"type":"string"}]}', - style: paramWithSchema.style, in: paramWithSchema.in, name: paramWithSchema.name, + style: paramWithSchema.style, explode: paramWithSchema.explode, - required: paramWithSchema.required, + required: Boolean(paramWithSchema.required), }, { schema: '{}', - style: 'form', in: paramWithoutSchema.in, name: paramWithoutSchema.name, + style: 'form', explode: false, required: false, }, @@ -291,21 +292,23 @@ 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 = { - config: {}, + const plugin: RequestValidatorPlugin = { + name: 'request-validator', + config: { + body_schema: ALLOW_ALL_SCHEMA, + }, }; const generated = generateRequestValidatorPlugin({ plugin, tags }); - expect(generated.config).toStrictEqual({ + expect(generated.config).toStrictEqual({ version: 'draft4', - body_schema: '{}', + body_schema: ALLOW_ALL_SCHEMA, }); }); it('should return default if no operation request body content defined', () => { - const plugin = {}; - const defaultReqVal = { + const defaultReqVal: RequestValidatorPlugin['config'] = { version: 'draft4', - body_schema: '{}', + body_schema: ALLOW_ALL_SCHEMA, }; const op1: OA3Operation = { requestBody: {}, @@ -315,19 +318,18 @@ describe('plugins', () => { $ref: 'non-existent', }, }; - expect(generateRequestValidatorPlugin({ plugin, operation: op1, tags }).config).toStrictEqual( + expect(generateRequestValidatorPlugin({ operation: op1, tags }).config).toStrictEqual( defaultReqVal, ); - expect(generateRequestValidatorPlugin({ plugin, operation: op2, tags }).config).toStrictEqual( + expect(generateRequestValidatorPlugin({ operation: op2, tags }).config).toStrictEqual( defaultReqVal, ); - expect(generateRequestValidatorPlugin({ plugin, tags }).config).toStrictEqual( + expect(generateRequestValidatorPlugin({ tags }).config).toStrictEqual( defaultReqVal, ); }); it('should add non-json media types to allowed content types and not add body schema', () => { - const plugin = {}; const operation: OA3Operation = { requestBody: { content: { @@ -336,16 +338,15 @@ describe('plugins', () => { }, }, }; - const generated = generateRequestValidatorPlugin({ plugin, tags, operation }); - expect(generated.config).toStrictEqual({ + const generated = generateRequestValidatorPlugin({ tags, operation }); + expect(generated.config).toStrictEqual({ version: 'draft4', - body_schema: '{}', + body_schema: ALLOW_ALL_SCHEMA, allowed_content_types: ['application/xml', 'text/yaml'], }); }); it('should add body_schema and allowed content types', () => { - const plugin = {}; const schemaXml = { type: 'Object', properties: { @@ -376,8 +377,8 @@ describe('plugins', () => { }, }, }; - const generated = generateRequestValidatorPlugin({ plugin, tags, operation }); - expect(generated.config).toStrictEqual({ + const generated = generateRequestValidatorPlugin({ tags, operation }); + expect(generated.config).toStrictEqual({ version: 'draft4', body_schema: JSON.stringify(schemaJson), allowed_content_types: ['application/xml', 'application/json'], @@ -385,10 +386,10 @@ describe('plugins', () => { }); it('should default body_schema if no schema is defined or generated', () => { - const generated = generateRequestValidatorPlugin({ plugin: {}, tags, operation: {} }); - expect(generated.config).toStrictEqual({ + const generated = generateRequestValidatorPlugin({ tags, operation: {} }); + expect(generated.config).toStrictEqual({ version: 'draft4', - body_schema: '{}', + body_schema: ALLOW_ALL_SCHEMA, }); }); }); diff --git a/packages/openapi-2-kong/src/declarative-config/plugins.ts b/packages/openapi-2-kong/src/declarative-config/plugins.ts index bcbf6d648..febfaf0a5 100644 --- a/packages/openapi-2-kong/src/declarative-config/plugins.ts +++ b/packages/openapi-2-kong/src/declarative-config/plugins.ts @@ -1,11 +1,12 @@ import { Entry } from 'type-fest'; import { distinctByProperty, getPluginNameFromKey, isPluginKey } from '../common'; -import { DCPlugin, DCPluginConfig } from '../types/declarative-config'; +import { DCPlugin } from '../types/declarative-config'; +import { isBodySchema, isParameterSchema, ParameterSchema, RequestValidatorPlugin, xKongPluginRequestValidator, XKongPluginRequestValidator } from '../types/kong'; import { OA3Operation, OpenApi3Spec, OA3RequestBody, OA3Parameter } from '../types/openapi3'; -export function isRequestValidatorPluginKey(key: string) { - return key.match(/-request-validator$/) != null; -} +export const isRequestValidatorPluginKey = (property: string): property is typeof xKongPluginRequestValidator => ( + property.match(/-request-validator$/) != null +); type PluginItem = Record; @@ -30,10 +31,10 @@ const generatePlugin = (tags: string[]) => ([key, value]: Entry): DC }); /** - This is valid config to allow all content to pass - See: https://github.com/Kong/kong-plugin-enterprise-request-validator/pull/34/files#diff-1a1d2d5ce801cc1cfb2aa91ae15686d81ef900af1dbef00f004677bc727bfd3cR284 + * This is valid config to allow all content to pass + * See: https://github.com/Kong/kong-plugin-enterprise-request-validator/pull/34/files#diff-1a1d2d5ce801cc1cfb2aa91ae15686d81ef900af1dbef00f004677bc727bfd3cR284 */ -const ALLOW_ALL_SCHEMA = '{}'; +export const ALLOW_ALL_SCHEMA = '{}'; const DEFAULT_PARAM_STYLE = { header: 'simple', @@ -47,7 +48,7 @@ const generateParameterSchema = (operation?: OA3Operation) => { return undefined; } - const parameterSchema: OA3Parameter[] = []; + const parameterSchemas: ParameterSchema[] = []; for (const parameter of operation.parameters as OA3Parameter[]) { // The following is valid config to allow all content to pass, in the case where schema is not defined let schema = ''; @@ -69,17 +70,18 @@ const generateParameterSchema = (operation?: OA3Operation) => { throw new Error(`invalid 'in' property (parameter '${name}')`); } - parameterSchema.push({ + const parameterSchema: ParameterSchema = { in: parameter.in, explode: !!parameter.explode, required: !!parameter.required, name: parameter.name, schema, style: paramStyle, - }); + }; + parameterSchemas.push(parameterSchema); } - return parameterSchema; + return parameterSchemas; }; function generateBodyOptions(operation?: OA3Operation) { @@ -103,23 +105,26 @@ function generateBodyOptions(operation?: OA3Operation) { }; } -export function generateRequestValidatorPlugin({ plugin, tags, operation }: { - plugin: Record, +export function generateRequestValidatorPlugin({ + plugin = { name: 'request-validator' }, + tags, + operation, +}: { + plugin?: Partial, tags: string[], operation?: OA3Operation, }) { - const config: DCPluginConfig = { + const config: Partial = { version: 'draft4', }; - const pluginConfig = plugin.config ?? {}; + // // Use original or generated parameter_schema + const parameterSchema = isParameterSchema(plugin.config) ? plugin.config.parameter_schema : generateParameterSchema(operation); - // Use original or generated parameter_schema - const parameterSchema = pluginConfig.parameter_schema ?? generateParameterSchema(operation); const generated = generateBodyOptions(operation); // Use original or generated body_schema - let bodySchema = pluginConfig.body_schema ?? generated.bodySchema; + let bodySchema = isBodySchema(plugin.config) ? plugin.config.body_schema : generated.bodySchema; // If no parameter_schema or body_schema is defined or generated, allow all content to pass if (parameterSchema === undefined && bodySchema === undefined) { @@ -130,62 +135,63 @@ export function generateRequestValidatorPlugin({ plugin, tags, operation }: { if (parameterSchema !== undefined) { config.parameter_schema = parameterSchema; } - if (bodySchema !== undefined) { config.body_schema = bodySchema; } // Use original or generated allowed_content_types - const allowedContentTypes = pluginConfig.allowed_content_types ?? generated.allowedContentTypes; + const allowedContentTypes = plugin.config?.allowed_content_types ?? generated.allowedContentTypes; if (allowedContentTypes !== undefined) { config.allowed_content_types = allowedContentTypes; } // Use original verbose_response if defined - if (pluginConfig.verbose_response !== undefined) { - config.verbose_response = Boolean(pluginConfig.verbose_response); + if (plugin.config?.verbose_response !== undefined) { + config.verbose_response = Boolean(plugin.config.verbose_response); } const isEnabledSpecified = Object.prototype.hasOwnProperty.call(plugin, 'enabled'); const enabled = isEnabledSpecified ? { enabled: Boolean(plugin.enabled ?? true) } : {}; - const dcPlugin: DCPlugin = { - config, + const requestValidatorPlugin: RequestValidatorPlugin = { + name: 'request-validator', + config: config as RequestValidatorPlugin['config'], tags: [ ...(tags ?? []), ...(plugin.tags ?? []), ], ...enabled, - name: 'request-validator', }; - return dcPlugin; + return requestValidatorPlugin; } export function generateGlobalPlugins(api: OpenApi3Spec, tags: string[]) { const globalPlugins = generatePlugins(api, tags); - const requestValidatorPlugin = getRequestValidatorPluginDirective(api); + const plugin = getRequestValidatorPluginDirective(api); - if (requestValidatorPlugin) { - globalPlugins.push(generateRequestValidatorPlugin({ plugin: requestValidatorPlugin, tags })); + if (plugin) { + globalPlugins.push(generateRequestValidatorPlugin({ plugin, tags })); } return { // Server plugins take precedence over global plugins plugins: distinctByProperty(globalPlugins, plugin => plugin.name), - requestValidatorPlugin, + requestValidatorPlugin: plugin, }; } export const generateOperationPlugins = ({ operation, pathPlugins, parentValidatorPlugin, tags }: { operation: OA3Operation, pathPlugins: DCPlugin[], - parentValidatorPlugin?: Record | null, + parentValidatorPlugin?: RequestValidatorPlugin | null, tags: string[], }) => { const operationPlugins = generatePlugins(operation, tags); - // Check if validator plugin exists on the operation + + // Check if validator plugin exists on the operation, even if the value of the plugin is undefined const operationValidatorPlugin = getRequestValidatorPluginDirective(operation); + // Use the operation or parent validator plugin, or skip if neither exist const plugin = operationValidatorPlugin || parentValidatorPlugin; @@ -197,8 +203,9 @@ export const generateOperationPlugins = ({ operation, pathPlugins, parentValidat return distinctByProperty([...operationPlugins, ...pathPlugins], plugin => plugin.name); }; -export function getRequestValidatorPluginDirective(obj: Record) { - const key = Object.keys(obj).filter(isPluginKey).find(isRequestValidatorPluginKey); - // If the key is defined but is blank (therefore should be fully generated) then default to {} - return key ? (obj[key] || {}) as Record : null; -} +/** This function accepts any OpenAPI3 document segment that can have the request validator plugin and returns it if found */ +export const getRequestValidatorPluginDirective = (segment: XKongPluginRequestValidator) => { + const key = Object.keys(segment).filter(isPluginKey).find(isRequestValidatorPluginKey); + // If the key is defined but is blank (therefore should be fully generated) then default to {}, which is truthy. + return key ? (segment[key] || {} as RequestValidatorPlugin) : undefined; +}; 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 7b7a897cb..6ad607591 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 @@ -1,5 +1,6 @@ import { OA3SecurityScheme } from '../types/openapi3'; import { generateSecurityPlugin } from './security-plugins'; +import { tags } from './jest/test-helpers'; describe('security-plugins', () => { describe('generateSecurityPlugin()', () => { @@ -10,10 +11,10 @@ describe('security-plugins', () => { name: 'x-api-key', }; - const result = generateSecurityPlugin(scheme, [], ['Tag']); + const result = generateSecurityPlugin(scheme, [], tags); expect(result).toEqual({ name: 'key-auth', - tags: ['Tag'], + tags, config: { key_names: ['x-api-key'], }, @@ -28,10 +29,10 @@ describe('security-plugins', () => { name: 'x-api-key', }; - const result = generateSecurityPlugin(scheme, [], ['Tag']); + const result = generateSecurityPlugin(scheme, [], tags); expect(result).toEqual({ name: 'key-auth', - tags: ['Tag'], + tags, config: { key_names: ['x-api-key'], }, diff --git a/packages/openapi-2-kong/src/declarative-config/security-plugins.ts b/packages/openapi-2-kong/src/declarative-config/security-plugins.ts index b948a76c6..0fb66507b 100644 --- a/packages/openapi-2-kong/src/declarative-config/security-plugins.ts +++ b/packages/openapi-2-kong/src/declarative-config/security-plugins.ts @@ -1,5 +1,6 @@ import { getSecurity } from '../common'; import { DCPlugin } from '../types/declarative-config'; +import { BasicAuthPlugin, KeyAuthPlugin, OpenIDConnectPlugin } from '../types/kong'; import { OA3Operation, OpenApi3Spec, OA3SecurityScheme, OA3SecuritySchemeApiKey, OA3SecuritySchemeHttp, OA3SecuritySchemeOpenIdConnect } from '../types/openapi3'; export function generateSecurityPlugins( @@ -27,7 +28,7 @@ export function generateSecurityPlugins( return plugins; } -export function generateApiKeySecurityPlugin(scheme: OA3SecuritySchemeApiKey): DCPlugin { +export const generateApiKeySecurityPlugin = (scheme: OA3SecuritySchemeApiKey) => { if (!['query', 'header', 'cookie'].includes(scheme.in)) { throw new Error(`a ${scheme.type} object expects valid "in" property. Got ${scheme.in}`); } @@ -35,50 +36,45 @@ export function generateApiKeySecurityPlugin(scheme: OA3SecuritySchemeApiKey): D if (!scheme.name) { throw new Error(`a ${scheme.type} object expects valid "name" property. Got ${scheme.name}`); } - - return { + const keyAuthPlugin: KeyAuthPlugin = { name: 'key-auth', config: { key_names: [scheme.name], }, }; -} + return keyAuthPlugin; +}; -export function generateHttpSecurityPlugin(scheme: OA3SecuritySchemeHttp): DCPlugin { +export const generateBasicAuthPlugin = (scheme: OA3SecuritySchemeHttp) => { if ((scheme.scheme || '').toLowerCase() !== 'basic') { throw new Error(`Only "basic" http scheme supported. got ${scheme.scheme}`); } - - return { + const basicAuthPlugin: BasicAuthPlugin = { name: 'basic-auth', }; -} + return basicAuthPlugin; +}; -export function generateOpenIdConnectSecurityPlugin( - scheme: OA3SecuritySchemeOpenIdConnect, - args: string[], -): DCPlugin { +export const generateOpenIdConnectSecurityPlugin = (scheme: OA3SecuritySchemeOpenIdConnect, args: string[]) => { if (!scheme.openIdConnectUrl) { throw new Error(`invalid "openIdConnectUrl" property. Got ${scheme.openIdConnectUrl}`); } - - return { + const openIdConnectPlugin: OpenIDConnectPlugin = { name: 'openid-connect', config: { issuer: scheme.openIdConnectUrl, scopes_required: args || [], }, }; -} + return openIdConnectPlugin; +}; -export function generateOAuth2SecurityPlugin(): DCPlugin { - return { - config: { - auth_methods: ['client_credentials'], - }, - name: 'openid-connect', - }; -} +export const generateOAuth2SecurityPlugin = (): OpenIDConnectPlugin => ({ + config: { + auth_methods: ['client_credentials'], + }, + name: 'openid-connect', +}); export function generateSecurityPlugin( scheme: OA3SecurityScheme | null, @@ -94,7 +90,7 @@ export function generateSecurityPlugin( break; case 'http': - plugin = generateHttpSecurityPlugin(scheme as OA3SecuritySchemeHttp); + plugin = generateBasicAuthPlugin(scheme as OA3SecuritySchemeHttp); break; case 'openidconnect': 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 878979768..c2e20d5a9 100644 --- a/packages/openapi-2-kong/src/declarative-config/services.test.ts +++ b/packages/openapi-2-kong/src/declarative-config/services.test.ts @@ -1,8 +1,8 @@ import { generateServices } from './services'; -import { parseSpec } from '../generate'; -import { getSpec } from './utils'; import { DCRoute, DCService } from '../types/declarative-config'; -import { xKongPluginKeyAuth, xKongPluginRequestValidator, xKongRouteDefaults } from '../types'; +import { OA3Operation } from '../types'; +import { xKongPluginKeyAuth, xKongPluginRequestValidator, xKongRouteDefaults } from '../types/kong'; +import { tags, getSpec } from './jest/test-helpers'; /** This function is written in such a way as to allow mutations in tests but without affecting other tests. */ const getSpecResult = (): DCService => @@ -14,35 +14,35 @@ const getSpecResult = (): DCService => path: '/path', port: 443, protocol: 'https', - tags: ['Tag'], + tags, routes: [ { name: 'My_API-Cat_stuff-post', strip_path: false, methods: ['POST'], paths: ['/cats$'], - tags: ['Tag'], + tags, }, { name: 'My_API-dogs-get', strip_path: false, methods: ['GET'], paths: ['/dogs$'], - tags: ['Tag'], + tags, }, { name: 'My_API-dogs-post', strip_path: false, methods: ['POST'], paths: ['/dogs$'], - tags: ['Tag'], + tags, }, { name: 'My_API-birds_id-get', strip_path: false, methods: ['GET'], paths: ['/birds/(?[^\\/\\s]+)$'], - tags: ['Tag'], + tags, }, ], }), @@ -50,134 +50,131 @@ const getSpecResult = (): DCService => describe('services', () => { describe('error states and validation', () => { - it('fails with no servers', async () => { + it('fails with no servers', () => { const spec = getSpec(); delete spec.servers; - const api = await parseSpec(spec); - const fn = () => generateServices(api, ['Tag']); + const fn = () => generateServices(spec, tags); expect(fn).toThrowError('no servers defined in spec'); }); - it('throws for a root level x-kong-route-default', async () => { + it('throws for a root level x-kong-route-default', () => { const spec = getSpec({ // @ts-expect-error intentionally invalid [xKongRouteDefaults]: 'foo', }); - const api = await parseSpec(spec); - const fn = () => generateServices(api, ['Tag']); + const fn = () => generateServices(spec, tags); expect(fn).toThrowError('expected root-level \'x-kong-route-defaults\' to be an object'); }); - it('ignores null for a root level x-kong-route-default', async () => { + it('ignores null for a root level x-kong-route-default', () => { const spec = getSpec({ // @ts-expect-error intentionally invalid [xKongRouteDefaults]: null, }); const specResult = getSpecResult(); - const api = await parseSpec(spec); - expect(generateServices(api, ['Tag'])).toEqual([specResult]); + expect(generateServices(spec, tags)).toEqual([specResult]); }); - it('throws for a paths level x-kong-route-default', async () => { + it('throws for a paths level x-kong-route-default', () => { const spec = getSpec({}); // @ts-expect-error intentionally invalid spec.paths['/cats'][xKongRouteDefaults] = 'foo'; - const api = await parseSpec(spec); - const fn = () => generateServices(api, ['Tag']); + const fn = () => generateServices(spec, tags); expect(fn).toThrowError('expected \'x-kong-route-defaults\' to be an object (at path \'/cats\')'); }); - it('ignores null for a paths level x-kong-route-default', async () => { + it('ignores null for a paths level x-kong-route-default', () => { const spec = getSpec(); // @ts-expect-error intentionally invalid spec.paths['/cats'][xKongRouteDefaults] = null; const specResult = getSpecResult(); - const api = await parseSpec(spec); - expect(generateServices(api, ['Tag'])).toEqual([specResult]); + expect(generateServices(spec, tags)).toEqual([specResult]); }); - it('throws for an operation level x-kong-route-default', async () => { + it('throws for an operation level x-kong-route-default', () => { const spec = getSpec(); // @ts-expect-error intentionally invalid spec.paths['/cats'].post[xKongRouteDefaults] = 'foo'; - const api = await parseSpec(spec); - const fn = () => generateServices(api, ['Tag']); + const fn = () => generateServices(spec, tags); expect(fn).toThrowError( 'expected \'x-kong-route-defaults\' to be an object (at operation \'post\' of path \'/cats\')', ); }); - it('ignores null for an operation level x-kong-route-default', async () => { + it('ignores null for an operation level x-kong-route-default', () => { const spec = getSpec(); // @ts-expect-error intentionally invalid spec.paths['/cats'].post[xKongRouteDefaults] = null; const specResult = getSpecResult(); - const api = await parseSpec(spec); - expect(generateServices(api, ['Tag'])).toEqual([specResult]); + expect(generateServices(spec, tags)).toEqual([specResult]); }); }); describe('generateServices()', () => { - it('generates generic service with paths', async () => { + it('generates generic service with paths', () => { const spec = getSpec(); const specResult = getSpecResult(); - const api = await parseSpec(spec); - expect(generateServices(api, ['Tag'])).toEqual([specResult]); + expect(generateServices(spec, tags)).toEqual([specResult]); }); - it('generates routes with request validator plugin from operation over path over global', async () => { + it('generates routes with request validator plugin from operation over path over global', () => { const spec = getSpec({ // global req validator plugin [xKongPluginRequestValidator]: { + name: 'request-validator', config: { + // @ts-expect-error use body_schema instead parameter_schema: 'global', }, }, }); // path req validator plugin spec.paths['/cats'][xKongPluginRequestValidator] = { + name: 'request-validator', config: { + // @ts-expect-error use body_schema instead parameter_schema: 'path', }, }; spec.paths['/cats'].get = {}; // operation req validator plugin - if (spec.paths['/cats'].post) { - spec.paths['/cats'].post[xKongPluginRequestValidator] = { - config: { - parameter_schema: 'operation', - }, - }; - } + (spec.paths['/cats'].post as OA3Operation)[xKongPluginRequestValidator] = { + name: 'request-validator', + config: { + // @ts-expect-error use body_schema instead + parameter_schema: 'operation', + }, + }; // operation req validator plugin - if (spec.paths['/dogs'].post) { - spec.paths['/dogs'].post[xKongPluginRequestValidator] = { - config: { - parameter_schema: 'operation', - }, - }; - } + (spec.paths['/dogs'].post as OA3Operation)[xKongPluginRequestValidator] = { + name: 'request-validator', + config: { + // @ts-expect-error use body_schema instead + parameter_schema: 'operation', + }, + }; const specResult = getSpecResult(); specResult.plugins = [ { config: { version: 'draft4', + // @ts-expect-error use body_schema instead parameter_schema: 'global', }, - tags: ['Tag'], + tags, name: 'request-validator', }, ]; specResult.routes = [ { - tags: ['Tag'], + tags, name: 'My_API-Cat_stuff-post', methods: ['POST'], paths: ['/cats$'], @@ -187,15 +184,16 @@ describe('services', () => { // should have operation plugin config: { version: 'draft4', + // @ts-expect-error use body_schema instead parameter_schema: 'operation', }, - tags: ['Tag'], + tags, name: 'request-validator', }, ], }, { - tags: ['Tag'], + tags, name: 'My_API-Cat_stuff-get', methods: ['GET'], paths: ['/cats$'], @@ -205,15 +203,16 @@ describe('services', () => { // should apply path plugin config: { version: 'draft4', + // @ts-expect-error use body_schema instead parameter_schema: 'path', }, - tags: ['Tag'], + tags, name: 'request-validator', }, ], }, { - tags: ['Tag'], + tags, name: 'My_API-dogs-get', methods: ['GET'], paths: ['/dogs$'], @@ -223,15 +222,16 @@ describe('services', () => { // should apply global plugin config: { version: 'draft4', + // @ts-expect-error use body_schema instead parameter_schema: 'global', }, - tags: ['Tag'], + tags, name: 'request-validator', }, ], }, { - tags: ['Tag'], + tags, name: 'My_API-dogs-post', methods: ['POST'], paths: ['/dogs$'], @@ -241,15 +241,16 @@ describe('services', () => { // should have operation plugin config: { version: 'draft4', + // @ts-expect-error use body_schema instead parameter_schema: 'operation', }, - tags: ['Tag'], + tags, name: 'request-validator', }, ], }, { - tags: ['Tag'], + tags, name: 'My_API-birds_id-get', methods: ['GET'], paths: ['/birds/(?[^\\/\\s]+)$'], @@ -259,24 +260,25 @@ describe('services', () => { // should apply global plugin config: { version: 'draft4', + // @ts-expect-error use body_schema instead parameter_schema: 'global', }, - tags: ['Tag'], + tags, name: 'request-validator', }, ], }, ]; - const api = await parseSpec(spec); - expect(generateServices(api, ['Tag'])).toEqual([specResult]); + expect(generateServices(spec, tags)).toEqual([specResult]); }); - it('generates routes with plugins from operation over path', async () => { + it('generates routes with plugins from operation over path', () => { const spec = getSpec(); spec.paths = { '/dogs': { summary: 'Dog stuff', [xKongPluginKeyAuth]: { + name: 'key-auth', config: { key_names: ['path'], }, @@ -299,11 +301,11 @@ describe('services', () => { strip_path: false, methods: ['GET'], paths: ['/dogs$'], - tags: ['Tag'], + tags, plugins: [ { name: 'key-auth', - tags: ['Tag'], + tags, // should apply path plugin config: { key_names: ['path'], @@ -316,11 +318,11 @@ describe('services', () => { strip_path: false, methods: ['POST'], paths: ['/dogs$'], - tags: ['Tag'], + tags, plugins: [ { name: 'key-auth', - tags: ['Tag'], + tags, // should apply path plugin config: { key_names: ['operation'], @@ -329,11 +331,10 @@ describe('services', () => { ], }, ]; - const api = await parseSpec(spec); - expect(generateServices(api, ['Tag'])).toEqual([specResult]); + expect(generateServices(spec, tags)).toEqual([specResult]); }); - it('replaces variables', async () => { + it('replaces variables', () => { const spec = getSpec(); spec.servers = [ { @@ -352,8 +353,7 @@ describe('services', () => { const specResult = getSpecResult(); specResult.port = 8443; specResult.path = '/v2'; - const api = await parseSpec(spec); - expect(generateServices(api, ['Tag'])).toEqual([specResult]); + expect(generateServices(spec, tags)).toEqual([specResult]); }); describe('x-kong-route-defaults and strip_path', () => { @@ -371,67 +371,56 @@ describe('services', () => { operationLevel: true, } as unknown as DCRoute; - it('root level', async () => { + it('root level', () => { const spec = getSpec({ [xKongRouteDefaults]: rootLevel, }); const specResult = getSpecResult(); specResult.routes = specResult.routes.map(route => ({ ...route, ...rootLevel })); - const api = await parseSpec(spec); - expect(generateServices(api, ['Tag'])).toEqual([specResult]); + expect(generateServices(spec, tags)).toEqual([specResult]); }); - it('path level', async () => { + it('path level', () => { const spec = getSpec(); spec.paths['/dogs'][xKongRouteDefaults] = pathLevel; const specResult = getSpecResult(); specResult.routes[1] = { ...specResult.routes[1], ...pathLevel }; specResult.routes[2] = { ...specResult.routes[2], ...pathLevel }; - const api = await parseSpec(spec); - expect(generateServices(api, ['Tag'])).toEqual([specResult]); + expect(generateServices(spec, tags)).toEqual([specResult]); }); - it('operation level', async () => { + it('operation level', () => { const spec = getSpec(); - if (spec.paths['/dogs'].get) { - spec.paths['/dogs'].get[xKongRouteDefaults] = operationLevel; - } + (spec.paths['/dogs'].get as OA3Operation)[xKongRouteDefaults] = operationLevel; const specResult = getSpecResult(); specResult.routes[1] = { ...specResult.routes[1], ...operationLevel }; - const api = await parseSpec(spec); - expect(generateServices(api, ['Tag'])).toEqual([specResult]); + expect(generateServices(spec, tags)).toEqual([specResult]); }); - it('will select (but not merge) the operation level over the root level', async () => { + it('will select (but not merge) the operation level over the root level', () => { const spec = getSpec({ [xKongRouteDefaults]: rootLevel, }); - if (spec.paths['/cats'].post) { - spec.paths['/cats'].post[xKongRouteDefaults] = operationLevel; - } + (spec.paths['/cats'].post as OA3Operation)[xKongRouteDefaults] = operationLevel; const specResult = getSpecResult(); specResult.routes = specResult.routes.map(route => ({ ...route, ...(route.paths[0] === '/cats$' ? operationLevel : rootLevel), })); - const api = await parseSpec(spec); - expect(generateServices(api, ['Tag'])).toEqual([specResult]); + expect(generateServices(spec, tags)).toEqual([specResult]); }); - it('will select (but not merge) the operation level over the path level', async () => { + it('will select (but not merge) the operation level over the path level', () => { const spec = getSpec(); spec.paths['/dogs'][xKongRouteDefaults] = pathLevel; - if (spec.paths['/dogs'].post) { - spec.paths['/dogs'].post[xKongRouteDefaults] = operationLevel; - } + (spec.paths['/dogs'].post as OA3Operation)[xKongRouteDefaults] = operationLevel; const specResult = getSpecResult(); specResult.routes[1] = { ...specResult.routes[1], ...pathLevel }; specResult.routes[2] = { ...specResult.routes[2], ...operationLevel }; - const api = await parseSpec(spec); - expect(generateServices(api, ['Tag'])).toEqual([specResult]); + expect(generateServices(spec, tags)).toEqual([specResult]); }); - it('will select (but not merge) the path level over the root level', async () => { + it('will select (but not merge) the path level over the root level', () => { const spec = getSpec({ [xKongRouteDefaults]: rootLevel, }); @@ -441,23 +430,19 @@ describe('services', () => { ...route, ...(route.paths[0] === '/cats$' ? pathLevel : rootLevel), })); - const api = await parseSpec(spec); - expect(generateServices(api, ['Tag'])).toEqual([specResult]); + expect(generateServices(spec, tags)).toEqual([specResult]); }); - it('allows overriding strip_path at the path level', async () => { + it('allows overriding strip_path at the path level', () => { const spec = getSpec(); - spec.paths['/cats'][xKongRouteDefaults] = { - strip_path: true, - }; + spec.paths['/cats'][xKongRouteDefaults] = { strip_path: true }; const specResult = getSpecResult(); const cats = specResult.routes.find(route => route.paths[0] === '/cats$') as DCRoute; cats.strip_path = true; - const api = await parseSpec(spec); - expect(generateServices(api, ['Tag'])).toEqual([specResult]); + expect(generateServices(spec, tags)).toEqual([specResult]); }); - it('allows overriding `strip_path` from `x-kong-route-defaults` at the root', async () => { + it('allows overriding `strip_path` from `x-kong-route-defaults` at the root', () => { const spec = getSpec({ [xKongRouteDefaults]: { strip_path: true, @@ -465,8 +450,7 @@ describe('services', () => { }); const specResult = getSpecResult(); specResult.routes = specResult.routes.map(route => ({ ...route, strip_path: true })); - const api = await parseSpec(spec); - expect(generateServices(api, ['Tag'])).toEqual([specResult]); + expect(generateServices(spec, tags)).toEqual([specResult]); }); }); }); diff --git a/packages/openapi-2-kong/src/declarative-config/services.ts b/packages/openapi-2-kong/src/declarative-config/services.ts index ca0f2876f..6eb893cd8 100644 --- a/packages/openapi-2-kong/src/declarative-config/services.ts +++ b/packages/openapi-2-kong/src/declarative-config/services.ts @@ -15,7 +15,8 @@ import { generatePlugins, } from './plugins'; import { DCService, DCRoute } from '../types/declarative-config'; -import { OpenApi3Spec, OA3Server, OA3PathItem, xKongServiceDefaults, xKongName } from '../types/openapi3'; +import { OpenApi3Spec, OA3Server, OA3PathItem } from '../types/openapi3'; +import { xKongName, xKongServiceDefaults } from '../types/kong'; export function generateServices(api: OpenApi3Spec, tags: string[]) { const servers = getAllServers(api); diff --git a/packages/openapi-2-kong/src/declarative-config/tags.test.ts b/packages/openapi-2-kong/src/declarative-config/tags.test.ts new file mode 100644 index 000000000..d155fd510 --- /dev/null +++ b/packages/openapi-2-kong/src/declarative-config/tags.test.ts @@ -0,0 +1,61 @@ +import { KeyAuthPlugin, xKongPluginKeyAuth } from '../types/kong'; +import { generatePlugins } from './plugins'; +import { generateServices } from './services'; +import { tags, getSpec } from './jest/test-helpers'; +import { generateUpstreams } from './upstreams'; + +describe('tags', () => { + it('test that tags are appended to Service entities', () => { + const spec = getSpec(); + const services = generateServices(spec, tags); + services.forEach(service => { + expect(service.tags).toEqual(tags); + }); + }); + + it('test that tags are appended to Route entities', () => { + const spec = getSpec(); + const services = generateServices(spec, tags); + services.forEach(service => { + service.routes.forEach(route => { + expect(route.tags).toEqual(tags); + }); + }); + }); + + it('test that tags are appended to Plugin entities', () => { + const pluginTags = ['pluginTag']; + const spec = getSpec(); + const keyAuthPlugin: KeyAuthPlugin = { + name: 'key-auth', + config: { + key_names: ['x-api-key'], + }, + tags: pluginTags, + }; + spec[xKongPluginKeyAuth] = keyAuthPlugin; + const plugins = generatePlugins(spec, tags); + const resultingTags = [...tags, ...pluginTags]; + plugins.forEach(plugin => { + expect(plugin.tags).toEqual(resultingTags); + }); + }); + + it('test that tags are appended to Upstream entities', () => { + const spec = getSpec(); + const upstreams = generateUpstreams(spec, tags); + upstreams.forEach(upstream => { + expect(upstream.tags).toEqual(tags); + }); + }); + + it('test that tags are appended to Target entities', () => { + const spec = getSpec(); + const upstreams = generateUpstreams(spec, tags); + upstreams.forEach(upstream => { + upstream.targets.forEach(target => { + expect(target.tags).toEqual(tags); + }); + }); + }); +}); 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 2ec2eadfb..223e5cb08 100644 --- a/packages/openapi-2-kong/src/declarative-config/upstreams.test.ts +++ b/packages/openapi-2-kong/src/declarative-config/upstreams.test.ts @@ -1,50 +1,47 @@ -import { parseSpec } from '../generate'; -import { xKongUpstreamDefaults } from '../types'; +import { DCUpstream } from '../types'; +import { xKongUpstreamDefaults } from '../types/kong'; import { generateUpstreams } from './upstreams'; -import { getSpec } from './utils'; +import { tags, getSpec } from './jest/test-helpers'; /** This function is written in such a way as to allow mutations in tests but without affecting other tests. */ -const getSpecResult = () => +const getSpecResult = (): DCUpstream => JSON.parse( JSON.stringify({ name: 'My_API', targets: [ { target: 'server1.com:443', - tags: ['Tag'], + tags, }, ], - tags: ['Tag'], + tags, }), ); describe('upstreams', () => { - it('generates an upstream', async () => { + it('generates an upstream', () => { const spec = getSpec(); const specResult = getSpecResult(); - const api = await parseSpec(spec); - expect(generateUpstreams(api, ['Tag'])).toEqual([specResult]); + expect(generateUpstreams(spec, tags)).toEqual([specResult]); }); - it('throws for a root level x-kong-route-default', async () => { + it('throws for a root level x-kong-route-default', () => { const spec = getSpec({ - // @ts-expect-error intentionall invalid + // @ts-expect-error intentionally invalid [xKongUpstreamDefaults]: 'foo', }); - const api = await parseSpec(spec); - const fn = () => generateUpstreams(api, ['Tag']); + const fn = () => generateUpstreams(spec, tags); expect(fn).toThrowError(`expected '${xKongUpstreamDefaults}' to be an object`); }); - it('ignores null for a root level x-kong-route-default', async () => { + it('ignores null for a root level x-kong-route-default', () => { const spec = getSpec({ - // @ts-expect-error intentionall invalid + // @ts-expect-error intentionally invalid [xKongUpstreamDefaults]: null, }); const specResult = getSpecResult(); - const api = await parseSpec(spec); - expect(generateUpstreams(api, ['Tag'])).toEqual([specResult]); + expect(generateUpstreams(spec, tags)).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 bceb5384b..a7c8e84bf 100644 --- a/packages/openapi-2-kong/src/declarative-config/upstreams.ts +++ b/packages/openapi-2-kong/src/declarative-config/upstreams.ts @@ -1,6 +1,7 @@ import { getName, parseUrl, fillServerVariables } from '../common'; import { DCUpstream } from '../types/declarative-config'; -import { OpenApi3Spec, xKongUpstreamDefaults } from '../types/openapi3'; +import { xKongUpstreamDefaults } from '../types/kong'; +import { OpenApi3Spec } from '../types/openapi3'; export function generateUpstreams(api: OpenApi3Spec, tags: string[]) { const servers = api.servers || []; diff --git a/packages/openapi-2-kong/src/declarative-config/utils.ts b/packages/openapi-2-kong/src/declarative-config/utils.ts deleted file mode 100644 index 67036839a..000000000 --- a/packages/openapi-2-kong/src/declarative-config/utils.ts +++ /dev/null @@ -1,36 +0,0 @@ -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 = (overrides: Partial = {}): OpenApi3Spec => - JSON.parse( - JSON.stringify({ - openapi: '3.0', - info: { - version: '1.0', - title: 'My API', - }, - servers: [ - { - url: 'https://server1.com/path', - }, - ], - paths: { - '/cats': { - 'x-kong-name': 'Cat stuff', - summary: 'summary is ignored', - post: {}, - }, - '/dogs': { - summary: 'Dog stuff', - get: {}, - post: { - summary: 'Ignored summary', - }, - }, - '/birds/{id}': { - get: {}, - }, - }, - ...overrides, - }), - ); diff --git a/packages/openapi-2-kong/src/generate.test.ts b/packages/openapi-2-kong/src/generate.test.ts index 29475165b..9c405ff15 100644 --- a/packages/openapi-2-kong/src/generate.test.ts +++ b/packages/openapi-2-kong/src/generate.test.ts @@ -90,7 +90,7 @@ describe('top-level API exports', () => { const parsedSpec = YAML.parse(dcFixtureFileString); const { documents: [dc], - } = await generateFromSpec(parsedSpec, 'kong-declarative-config') as DeclarativeConfigResult; + } = generateFromSpec(parsedSpec, 'kong-declarative-config') as DeclarativeConfigResult; expect(dc._format_version).toBe('1.1'); expect(dc.services.length).toBe(1); expect(dc.upstreams.length).toBe(1); @@ -103,7 +103,7 @@ describe('top-level API exports', () => { label, documents, warnings, - } = await generateFromSpec(parsedSpec, 'kong-for-kubernetes') as KongForKubernetesResult; + } = generateFromSpec(parsedSpec, 'kong-for-kubernetes') as KongForKubernetesResult; expect(type).toBe('kong-for-kubernetes'); expect(label).toBe('Kong for Kubernetes'); expect(documents).toHaveLength(9); diff --git a/packages/openapi-2-kong/src/generate.ts b/packages/openapi-2-kong/src/generate.ts index 1d6cb357d..5c0343d13 100644 --- a/packages/openapi-2-kong/src/generate.ts +++ b/packages/openapi-2-kong/src/generate.ts @@ -14,51 +14,6 @@ export const conversionTypes: ConversionResultType[] = [ 'kong-for-kubernetes', ]; -export const generate = ( - specPath: string, - type: ConversionResultType, - tags: string[] = [], -) => new Promise((resolve, reject) => { - fs.readFile(path.resolve(specPath), 'utf8', (err, contents) => { - if (err != null) { - reject(err); - return; - } - - const fileSlug = path.basename(specPath); - const allTags = [`OAS3file_${fileSlug}`, ...tags]; - resolve(generateFromString(contents, type, allTags)); - }); -}); - -export const generateFromString = async ( - specStr: string, - type: ConversionResultType, - tags: string[] = [], -) => { - const api = await parseSpec(specStr); - return generateFromSpec(api, type, tags); -}; - -export const generateFromSpec = ( - api: OpenApi3Spec, - type: ConversionResultType, - tags: string[] = [], -) => { - const allTags = [...defaultTags, ...tags]; - - switch (type) { - case 'kong-declarative-config': - return generateDeclarativeConfigFromSpec(api, allTags); - - case 'kong-for-kubernetes': - return generateKongForKubernetesConfigFromSpec(api); - - default: - throw new Error(`Unsupported output type "${type}"`); - } -}; - export const parseSpec = (spec: string | Record) => { let api: OpenApi3Spec; @@ -81,6 +36,51 @@ export const parseSpec = (spec: string | Record) => { api.openapi = '3.0.0'; } - // @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) + // @ts-expect-error until we make our OpenAPI type extend from the canonical one (i.e. from `openapi-types`, we'll need to shim this here) return SwaggerParser.dereference(api) as Promise; }; + +export const generateFromSpec = ( + api: OpenApi3Spec, + type: ConversionResultType, + tags: string[] = [], +) => { + const allTags = [...defaultTags, ...tags]; + + switch (type) { + case 'kong-declarative-config': + return generateDeclarativeConfigFromSpec(api, allTags); + + case 'kong-for-kubernetes': + return generateKongForKubernetesConfigFromSpec(api); + + default: + throw new Error(`Unsupported output type "${type}"`); + } +}; + +export const generateFromString = async ( + specStr: string, + type: ConversionResultType, + tags: string[] = [], +) => { + const api = await parseSpec(specStr); + return generateFromSpec(api, type, tags); +}; + +export const generate = ( + filePath: string, + type: ConversionResultType, + tags: string[] = [], +) => new Promise((resolve, reject) => { + fs.readFile(path.resolve(filePath), 'utf8', (err, contents) => { + if (err != null) { + reject(err); + return; + } + + const fileSlug = path.basename(filePath); + const allTags = [`OAS3file_${fileSlug}`, ...tags]; + resolve(generateFromString(contents, type, allTags)); + }); +}); diff --git a/packages/openapi-2-kong/src/index.ts b/packages/openapi-2-kong/src/index.ts index 555cc6613..8f3c43d12 100644 --- a/packages/openapi-2-kong/src/index.ts +++ b/packages/openapi-2-kong/src/index.ts @@ -3,7 +3,6 @@ export { generate, generateFromString, generateFromSpec, - parseSpec, } from './generate'; export * from './types'; diff --git a/packages/openapi-2-kong/src/kubernetes/fixtures/cloud-api.expected.yaml b/packages/openapi-2-kong/src/kubernetes/fixtures/cloud-api.expected.yaml index 3130678bb..b68bb22d5 100644 --- a/packages/openapi-2-kong/src/kubernetes/fixtures/cloud-api.expected.yaml +++ b/packages/openapi-2-kong/src/kubernetes/fixtures/cloud-api.expected.yaml @@ -101,24 +101,23 @@ documents: annotations: cert-manager.io/cluster-issuer: letsencrypt external-dns.alpha.kubernetes.io/hostname: myserver - konghq.com/strip-path: "true" + konghq.com/strip-path: 'true' konghq.com/plugins: add-openid-connect-m3, add-aws-lambda-p2, add-pre-function-g1 konghq.com/override: get-method kubernetes.io/ingress.class: kong spec: tls: - hosts: - &a1 - myserver secretName: myserver-tls rules: - host: myserver http: paths: - - backend: - serviceName: my-cloud-api-service-0 - servicePort: 443 - path: /v1/offline-portal + - backend: + serviceName: my-cloud-api-service-0 + servicePort: 443 + path: /v1/offline-portal - apiVersion: extensions/v1beta1 kind: Ingress metadata: @@ -126,7 +125,7 @@ documents: annotations: cert-manager.io/cluster-issuer: letsencrypt external-dns.alpha.kubernetes.io/hostname: myserver - konghq.com/strip-path: "true" + konghq.com/strip-path: 'true' konghq.com/plugins: add-openid-connect-m5, add-aws-lambda-p4, add-pre-function-g1 konghq.com/override: get-method kubernetes.io/ingress.class: kong diff --git a/packages/openapi-2-kong/src/kubernetes/fixtures/cloud-api.yaml b/packages/openapi-2-kong/src/kubernetes/fixtures/cloud-api.yaml index a437cdc5d..4b4330fdf 100644 --- a/packages/openapi-2-kong/src/kubernetes/fixtures/cloud-api.yaml +++ b/packages/openapi-2-kong/src/kubernetes/fixtures/cloud-api.yaml @@ -9,9 +9,9 @@ info: annotations: cert-manager.io/cluster-issuer: letsencrypt external-dns.alpha.kubernetes.io/hostname: myserver - konghq.com/strip-path: "true" + konghq.com/strip-path: 'true' title: my Cloud API - version: "1.0.0" + version: '1.0.0' contact: name: myserver Support url: https://myserver/support @@ -24,44 +24,44 @@ info: ################################### # OpenID Connect Plugin for Kong API Authorization with Keycloak x-kong-plugin-openid-connect: - name: openid-connect - enabled: true - config: - issuer: - client_id: - - - client_secret: - - - auth_methods: ["authorization_code", "bearer", "session", "refresh_token"] - session_cookie_name: "cookie-name" - refresh_token_param_name: "Offline-Token" - scopes: ["openid", "profile", "email"] - upstream_user_info_header: "x-userinfo" - upstream_access_token_header: "access-token-name" + name: openid-connect + enabled: true + config: + issuer: + client_id: + - + client_secret: + - + auth_methods: ['authorization_code', 'bearer', 'session', 'refresh_token'] + session_cookie_name: 'cookie-name' + refresh_token_param_name: 'Offline-Token' + scopes: ['openid', 'profile', 'email'] + upstream_user_info_header: 'x-userinfo' + upstream_access_token_header: 'access-token-name' # Enable HTTPS redirect to disable HTTP access for the API server x-kong-plugin-https-redirect: name: pre-function config: functions: - - | - local scheme = kong.request.get_scheme() - if scheme == "http" then - local host = kong.request.get_host() - local query = kong.request.get_path_with_query() - local url = "https://" .. host ..query - kong.response.set_header("Location",url) - return kong.response.exit(302,url) - end + - | + local scheme = kong.request.get_scheme() + if scheme == "http" then + local host = kong.request.get_host() + local query = kong.request.get_path_with_query() + local url = "https://" .. host ..query + kong.response.set_header("Location",url) + return kong.response.exit(302,url) + end ############# ## Servers ## ############# servers: -- url: https://myserver/v1 - description: The non-production API Server for FRED Studio - x-kubernetes-tls: - - hosts: - - myserver - secretName: myserver-tls + - url: https://myserver/v1 + description: The non-production API Server for FRED Studio + x-kubernetes-tls: + - hosts: + - myserver + secretName: myserver-tls ################ ## Components ## ################ @@ -73,7 +73,7 @@ components: openIdConnectUrl: ## Required Scopes defined at the Operation Level ## schemas: - # define schemas here for components + # define schemas here for components # Schema for error response body Error: type: object @@ -85,9 +85,9 @@ components: required: - code - message -############################# -## Environment API Schemas ## -############################# + ############################# + ## Environment API Schemas ## + ############################# Quotas: type: object properties: @@ -97,9 +97,9 @@ components: type: object queuedBatches: type: object -####################### -## Batch API Schemas ## -####################### + ####################### + ## Batch API Schemas ## + ####################### # Model Upload URL Request Schema BatchModelUploadRequest: type: object @@ -123,7 +123,7 @@ components: containerLogLevel: type: string killTime: - type: integer + type: integer cliCommand: type: string batchConfig: @@ -212,9 +212,9 @@ components: ## Paths and API Operations ## ############################## paths: -#################### -## Root API Paths ## -#################### + #################### + ## Root API Paths ## + #################### '/offline-portal': x-kong-plugin-aws-lambda: name: aws-lambda @@ -255,8 +255,8 @@ paths: summary: Returns the User Info from the x-userinfo header forwarded upstream by Kong security: - StudioOIDC: - # an array of scopes required to perform the operation + # an array of scopes required to perform the operation - fred_read # This is created through Keycloak to enforce different levels of authorization (Scope not yet defined) responses: '200': - description: myserver API User Profile \ No newline at end of file + description: myserver API User Profile diff --git a/packages/openapi-2-kong/src/kubernetes/generate.test.ts b/packages/openapi-2-kong/src/kubernetes/generate.test.ts index edfe3fec2..3ed956752 100644 --- a/packages/openapi-2-kong/src/kubernetes/generate.test.ts +++ b/packages/openapi-2-kong/src/kubernetes/generate.test.ts @@ -1,6 +1,5 @@ -import { parseSpec } from '../generate'; import { K8sAnnotations, K8sIngressRule, K8sIngressTLS, K8sManifest } from '../types/kubernetes-config'; -import { OA3Server, OpenApi3Spec, xKongName } from '../types/openapi3'; +import { OA3Server } from '../types/openapi3'; import { generateKongForKubernetesConfigFromSpec, generateMetadataAnnotations, @@ -12,51 +11,35 @@ import { generateTLS, } from './generate'; import { - dummyName, - dummyPluginDoc, ingressDoc, ingressDocWithOverride, keyAuthName, keyAuthPluginDoc, methodDoc, - pluginDummy, pluginKeyAuth, } from './plugin-helpers'; +import { xKongName } from '../types/kong'; +import { dummyName, dummyPluginDoc, getSpec, pluginDummy } from '../declarative-config/jest/test-helpers'; describe('index', () => { - const spec: OpenApi3Spec = { - openapi: '3.0', - info: { - version: '1.0', - title: 'My API', - }, - servers: [ - { - url: 'http://api.insomnia.rest', - }, - ], - paths: {}, - }; - describe('getSpecName()', () => { - it('with info.title', async () => { - const api = await parseSpec({ ...spec }); - expect(getSpecName(api)).toBe('my-api'); + it('with info.title', () => { + const spec = getSpec(); + expect(getSpecName(spec)).toBe('my-api'); }); - it('no name', async () => { - const api = await parseSpec({ ...spec, info: undefined }); - expect(getSpecName(api)).toBe('openapi'); + it('no name', () => { + const spec = getSpec({ info: undefined }); + expect(getSpecName(spec)).toBe('openapi'); }); it('with x-kong-name', () => { - const api: OpenApi3Spec = { ...spec, [xKongName]: 'kong-name' }; - expect(getSpecName(api)).toBe('kong-name'); + const spec = getSpec({ [xKongName]: 'kong-name' }); + expect(getSpecName(spec)).toBe('kong-name'); }); - it('with x-kubernetes-ingress-metadata.name', async () => { - const api = await parseSpec({ - ...spec, + it('with x-kubernetes-ingress-metadata.name', () => { + const spec = getSpec({ [xKongName]: 'Kong Name', info: { 'x-kubernetes-ingress-metadata': { @@ -64,14 +47,13 @@ describe('index', () => { }, }, }); - expect(getSpecName(api)).toBe('k8s-name'); + expect(getSpecName(spec)).toBe('k8s-name'); }); }); describe('generateMetadataAnnotations()', () => { - it('gets annotations from x-kubernetes-ingress-metadata', async () => { - const api = await parseSpec({ - ...spec, + it('gets annotations from x-kubernetes-ingress-metadata', () => { + const spec = getSpec({ info: { 'x-kubernetes-ingress-metadata': { name: 'info-name', @@ -81,7 +63,7 @@ describe('index', () => { }, }, }); - const result = generateMetadataAnnotations(api, { + const result = generateMetadataAnnotations(spec, { pluginNames: [], }); expect(result).toEqual({ @@ -91,6 +73,7 @@ describe('index', () => { }); it('gets only core annotation(s)', () => { + const spec = getSpec(); const result = generateMetadataAnnotations(spec, { pluginNames: [], }); @@ -100,6 +83,7 @@ describe('index', () => { }); it('gets plugin annotations correctly', () => { + const spec = getSpec(); const result = generateMetadataAnnotations(spec, { pluginNames: ['one', 'two'], }); @@ -110,6 +94,7 @@ describe('index', () => { }); it('gets override annotation correctly', () => { + const spec = getSpec(); const result = generateMetadataAnnotations(spec, { pluginNames: [], overrideName: 'name', @@ -121,20 +106,18 @@ describe('index', () => { }); it('gets all annotations correctly', () => { - const originalAnnotations: K8sAnnotations = { + const annotations: K8sAnnotations = { 'nginx.ingress.kubernetes.io/rewrite-target': '/', }; - const api: OpenApi3Spec = { - ...spec, + const spec = getSpec({ info: { - ...spec.info, 'x-kubernetes-ingress-metadata': { name: 'info-name', - annotations: { ...originalAnnotations }, + annotations, }, }, - }; - const result = generateMetadataAnnotations(api, { + }); + const result = generateMetadataAnnotations(spec, { pluginNames: ['one', 'two'], overrideName: 'name', }); @@ -145,10 +128,8 @@ describe('index', () => { 'konghq.com/override': 'name', }); // Should not modify source metadata annotations object - const sourceMetadata = api.info['x-kubernetes-ingress-metadata']?.annotations; - expect(sourceMetadata).toStrictEqual( - originalAnnotations, - ); + const sourceMetadata = spec.info['x-kubernetes-ingress-metadata']?.annotations; + expect(sourceMetadata).toStrictEqual(annotations); }); }); @@ -275,6 +256,7 @@ describe('index', () => { }); describe('generateTLS', () => { + const spec = getSpec(); const server = spec.servers?.[0] as OA3Server; const ingressTLS: K8sIngressTLS[] = [{ secretName: 'ziltoid' }]; @@ -506,24 +488,25 @@ describe('index', () => { }); describe('generateKongForKubernetesConfigFromSpec()', () => { - it('handles global plugins', async () => { - const api = await parseSpec({ ...spec, ...pluginKeyAuth, ...pluginDummy }); - const result = generateKongForKubernetesConfigFromSpec(api); + const servers = [ + { + url: 'http://api.insomnia.rest', + }, + ]; + + it('handles global plugins', () => { + const spec = getSpec({ servers, ...pluginKeyAuth, ...pluginDummy }); + const result = generateKongForKubernetesConfigFromSpec(spec); expect(result.documents).toStrictEqual([ keyAuthPluginDoc('g0'), + // @ts-expect-error -- TSCONVERSION more work is needed to module augment to include DummyPlugin (but not export those augmentations) dummyPluginDoc('g1'), - ingressDoc( - 0, - [keyAuthName('g0'), dummyName('g1')], - 'api.insomnia.rest', - 'my-api-service-0', - ), + ingressDoc(0, [keyAuthName('g0'), dummyName('g1')], 'api.insomnia.rest', 'my-api-service-0'), ]); }); - it('handles global and server plugins', async () => { - const api = await parseSpec({ - ...spec, + it('handles global and server plugins', () => { + const spec = getSpec({ ...pluginKeyAuth, servers: [ { @@ -540,11 +523,12 @@ describe('index', () => { }, ], }); - const result = generateKongForKubernetesConfigFromSpec(api); + const result = generateKongForKubernetesConfigFromSpec(spec); expect(result.documents).toStrictEqual([ keyAuthPluginDoc('g0'), keyAuthPluginDoc('s1'), keyAuthPluginDoc('s2'), + // @ts-expect-error -- TSCONVERSION more work is needed to module augment to include DummyPlugin (but not export those augmentations) dummyPluginDoc('s3'), ingressDoc(0, [keyAuthName('g0')], 'api-0.insomnia.rest', 'my-api-service-0'), ingressDoc(1, [keyAuthName('s1')], 'api-1.insomnia.rest', 'my-api-service-1'), @@ -557,9 +541,9 @@ describe('index', () => { ]); }); - it('handles global and path plugins', async () => { - const api = await parseSpec({ - ...spec, + it('handles global and path plugins', () => { + const spec = getSpec({ + servers, ...pluginKeyAuth, paths: { '/no-plugin': {}, @@ -567,11 +551,12 @@ describe('index', () => { '/plugin-1': { ...pluginKeyAuth, ...pluginDummy }, }, }); - const result = generateKongForKubernetesConfigFromSpec(api); + const result = generateKongForKubernetesConfigFromSpec(spec); expect(result.documents).toStrictEqual([ keyAuthPluginDoc('g0'), keyAuthPluginDoc('p1'), keyAuthPluginDoc('p2'), + // @ts-expect-error -- TSCONVERSION more work is needed to module augment to include DummyPlugin (but not export those augmentations) dummyPluginDoc('p3'), ingressDoc(0, [keyAuthName('g0')], 'api.insomnia.rest', 'my-api-service-0', '/no-plugin'), ingressDoc(1, [keyAuthName('p1')], 'api.insomnia.rest', 'my-api-service-0', '/plugin-0'), @@ -585,9 +570,9 @@ describe('index', () => { ]); }); - it('handles global and method plugins', async () => { - const api = await parseSpec({ - ...spec, + it('handles global and method plugins', () => { + const spec = getSpec({ + servers, ...pluginKeyAuth, paths: { '/path': { @@ -597,7 +582,7 @@ describe('index', () => { }, }, }); - const result = generateKongForKubernetesConfigFromSpec(api); + const result = generateKongForKubernetesConfigFromSpec(spec); expect(result.documents).toStrictEqual([ methodDoc('get'), methodDoc('put'), @@ -605,6 +590,7 @@ describe('index', () => { keyAuthPluginDoc('g0'), keyAuthPluginDoc('m1'), keyAuthPluginDoc('m2'), + // @ts-expect-error -- TSCONVERSION more work is needed to module augment to include DummyPlugin (but not export those augmentations) dummyPluginDoc('m3'), ingressDocWithOverride( 0, diff --git a/packages/openapi-2-kong/src/kubernetes/generate.ts b/packages/openapi-2-kong/src/kubernetes/generate.ts index de33ede0e..e49ff4575 100644 --- a/packages/openapi-2-kong/src/kubernetes/generate.ts +++ b/packages/openapi-2-kong/src/kubernetes/generate.ts @@ -2,7 +2,7 @@ import { getMethodAnnotationName, getName, HttpMethodType, parseUrl } from '../c import urlJoin from 'url-join'; import { flattenPluginDocuments, getPlugins, prioritizePlugins } from './plugins'; import { pathVariablesToWildcard, resolveUrlVariables } from './variables'; -import { IndexIncrement } from '../types/k8splugins'; +import { IndexIncrement } from '../types/k8s-plugins'; import { K8sIngress, K8sKongIngress, K8sMetadata, K8sAnnotations, K8sIngressRule, K8sHTTPIngressPath, K8sIngressBackend } from '../types/kubernetes-config'; import { OpenApi3Spec, OA3Server } from '../types/openapi3'; import { KongForKubernetesResult } from '../types/outputs'; diff --git a/packages/openapi-2-kong/src/kubernetes/plugin-helpers.ts b/packages/openapi-2-kong/src/kubernetes/plugin-helpers.ts index 13f83aa34..59cc88f0d 100644 --- a/packages/openapi-2-kong/src/kubernetes/plugin-helpers.ts +++ b/packages/openapi-2-kong/src/kubernetes/plugin-helpers.ts @@ -1,6 +1,6 @@ import { HttpMethodType } from '../common'; -import { K8sIngress, K8sKongIngress, K8sKongPlugin } from '../types/kubernetes-config'; -import { xKongPluginKeyAuth, XKongPluginKeyAuth, XKongPluginUnknown } from '../types/openapi3'; +import { K8sIngress, K8sKongIngress, K8sKongPlugin, K8sKongPluginBase } from '../types/kubernetes-config'; +import { xKongPluginKeyAuth, XKongPluginKeyAuth, PluginBase } from '../types/kong'; export const pluginKeyAuth: XKongPluginKeyAuth = { [xKongPluginKeyAuth]: { @@ -13,23 +13,13 @@ export const pluginKeyAuth: XKongPluginKeyAuth = { }, }; -/** used only for testing */ -export const pluginDummy: XKongPluginUnknown<{ foo: 'bar' }> = { - 'x-kong-plugin-dummy-thing': { - name: 'dummy-thing', - config: { - foo: 'bar', - }, - }, -}; - -export const pluginDocWithName = (name: string, pluginType: string): K8sKongPlugin => ({ +export const pluginDocWithName = (name: string, plugin: string): K8sKongPluginBase> => ({ apiVersion: 'configuration.konghq.com/v1', kind: 'KongPlugin', metadata: { name, }, - plugin: pluginType, + plugin, }); export const keyAuthPluginDoc = (suffix: string): K8sKongPlugin => ({ @@ -46,18 +36,6 @@ export const keyAuthPluginDoc = (suffix: string): K8sKongPlugin => ({ plugin: 'key-auth', }); -export const dummyPluginDoc = (suffix: string): K8sKongPlugin => ({ - apiVersion: 'configuration.konghq.com/v1', - config: { - foo: 'bar', - }, - kind: 'KongPlugin', - metadata: { - name: dummyName(suffix), - }, - plugin: 'dummy-thing', -}); - export const methodDoc = (method: HttpMethodType | Lowercase): K8sKongIngress => ({ apiVersion: 'configuration.konghq.com/v1', kind: 'KongIngress', @@ -71,8 +49,6 @@ export const methodDoc = (method: HttpMethodType | Lowercase): K export const keyAuthName = (suffix: string) => `add-key-auth-${suffix}`; -export const dummyName = (suffix: string) => `add-dummy-thing-${suffix}`; - export const ingressDoc = ( index: number, plugins: string[], diff --git a/packages/openapi-2-kong/src/kubernetes/plugins.test.ts b/packages/openapi-2-kong/src/kubernetes/plugins.test.ts index f088adc42..f4e91f045 100644 --- a/packages/openapi-2-kong/src/kubernetes/plugins.test.ts +++ b/packages/openapi-2-kong/src/kubernetes/plugins.test.ts @@ -12,14 +12,20 @@ import { } from './plugins'; import { HttpMethod } from '../common'; import { - dummyPluginDoc, keyAuthPluginDoc, pluginDocWithName, - pluginDummy, pluginKeyAuth, } from './plugin-helpers'; -import { PathPlugin, OperationPlugin } from '../types/k8splugins'; -import { OpenApi3Spec, OA3Server, OA3Paths, OA3PathItem, OA3Components } from '../types/openapi3'; +import { PathPlugin, OperationPlugin } from '../types/k8s-plugins'; +import { + OpenApi3Spec, + OA3Server, + OA3Paths, + OA3PathItem, + OA3Components, + OA3Operation, +} from '../types/openapi3'; +import { dummyPluginDoc, pluginDummy, UserK8sPlugin } from '../declarative-config/jest/test-helpers'; describe('plugins', () => { let _iterator = 0; @@ -159,7 +165,7 @@ describe('plugins', () => { it('returns multiple plugin docs', () => { const api: OpenApi3Spec = { ...spec, ...pluginKeyAuth, ...pluginDummy }; const result = getGlobalPlugins(api, increment); - expect(result).toEqual([keyAuthPluginDoc('g0'), dummyPluginDoc('g1')]); + expect(result).toEqual([keyAuthPluginDoc('g0'), dummyPluginDoc('g1')]); }); it('returns security plugin doc', () => { @@ -284,7 +290,7 @@ describe('plugins', () => { it('should handle plugins existing on path', () => { const paths: OA3Paths = { '/path-no-plugin': {}, - '/path': { ...pluginDummy }, + '/path': pluginDummy as OA3PathItem, }; const result = getPathPlugins(paths, increment, spec); expect(result).toHaveLength(2); @@ -301,7 +307,7 @@ describe('plugins', () => { it('should handle plugins existing on operation and not on path', () => { const paths: OA3Paths = { '/path': { - get: { ...pluginDummy }, + get: pluginDummy as OA3Operation, put: {}, }, }; @@ -322,7 +328,7 @@ describe('plugins', () => { it('should handle plugins existing on path and operation', () => { const paths: OA3Paths = { - '/path-0': { ...pluginKeyAuth, get: { ...pluginDummy } }, + '/path-0': { ...pluginKeyAuth, get: pluginDummy as OA3Operation }, '/path-1': { ...pluginDummy, put: {} }, }; const result = getPathPlugins(paths, increment, spec); @@ -354,10 +360,9 @@ describe('plugins', () => { it('should return plugins for all operations on path', () => { const pathItem: OA3PathItem = { - // @ts-expect-error -- TSCONVERSION appears to be a genuine error. this actually passes 'GET' but I think 'get' is expected. [HttpMethod.get]: {}, [HttpMethod.put]: { ...pluginKeyAuth, ...pluginDummy }, - [HttpMethod.post]: { ...pluginDummy }, + [HttpMethod.post]: pluginDummy as OA3Operation, }; const result = getOperationPlugins(pathItem, increment, spec); expect(result).toHaveLength(3); @@ -389,7 +394,6 @@ describe('plugins', () => { it('should return security plugin from operation', () => { const api: OpenApi3Spec = { ...spec, components }; const pathItem: OA3PathItem = { - // @ts-expect-error -- TSCONVERSION appears to be a genuine error. this actually passes 'GET' but I think 'get' is expected. [HttpMethod.get]: { security: [ { @@ -446,6 +450,7 @@ describe('plugins', () => { const source: PathPlugin[] = [ { path: '/path-with-plugin', + // @ts-expect-error -- TSCONVERSION more work is needed to module augment to include DummyPlugin (but not export those augmentations) plugins: [dummyPluginDoc('p0')], operations: [blankOperation], }, @@ -466,6 +471,7 @@ describe('plugins', () => { operations: [ { method: HttpMethod.get, + // @ts-expect-error -- TSCONVERSION more work is needed to module augment to include DummyPlugin (but not export those augmentations) plugins: [dummyPluginDoc('p0')], }, { @@ -504,6 +510,7 @@ describe('plugins', () => { }, { method: HttpMethod.post, + // @ts-expect-error -- TSCONVERSION more work is needed to module augment to include DummyPlugin (but not export those augmentations) plugins: [dummyPluginDoc('p0')], }, ]; diff --git a/packages/openapi-2-kong/src/kubernetes/plugins.ts b/packages/openapi-2-kong/src/kubernetes/plugins.ts index f37a0c819..bbefa6b27 100644 --- a/packages/openapi-2-kong/src/kubernetes/plugins.ts +++ b/packages/openapi-2-kong/src/kubernetes/plugins.ts @@ -9,10 +9,11 @@ import { } from '../common'; import { generateSecurityPlugins } from '../declarative-config/security-plugins'; import { DCPlugin } from '../types/declarative-config'; -import { Plugins, IndexIncrement, ServerPlugin, PathPlugin, OperationPlugin } from '../types/k8splugins'; -import { K8sKongPlugin } from '../types/kubernetes-config'; +import { Plugins, IndexIncrement, ServerPlugin, PathPlugin, OperationPlugin } from '../types/k8s-plugins'; +import { K8sKongPlugin, K8sKongPluginBase } from '../types/kubernetes-config'; import { OpenApi3Spec, OA3Server, OA3Paths, OA3PathItem, OA3Operation } from '../types/openapi3'; import { ValueOf } from 'type-fest'; +import { PluginBase } from '../types/kong'; export function flattenPluginDocuments(plugins: Plugins): K8sKongPlugin[] { const all: K8sKongPlugin[] = []; @@ -33,7 +34,7 @@ export function flattenPluginDocuments(plugins: Plugins): K8sKongPlugin[] { export function getPlugins(api: OpenApi3Spec): Plugins { let _iterator = 0; - const increment = (): number => _iterator++; + const increment = () => _iterator++; const servers = getServers(api); @@ -192,19 +193,19 @@ export function normalizePathPlugins(pathPlugins: PathPlugin[]) { return pluginsExist ? pathPlugins : [blankPath]; } -export function normalizeOperationPlugins(operationPlugins: OperationPlugin[]) { - const pluginsExist = operationPlugins.some(o => o.plugins.length); +export const normalizeOperationPlugins = (operationPlugins: OperationPlugin[]) => { + const pluginsExist = operationPlugins.some(operation => operation.plugins.length); return pluginsExist ? operationPlugins : [blankOperation]; -} +}; -export function prioritizePlugins( - global: K8sKongPlugin[], - server: K8sKongPlugin[], - path: K8sKongPlugin[], - operation: K8sKongPlugin[], +export function prioritizePlugins>>( + global: T[], + server: T[], + path: T[], + operation: T[], ) { // Order in priority: operation > path > server > global - const plugins: K8sKongPlugin[] = [...operation, ...path, ...server, ...global]; + const plugins: T[] = [...operation, ...path, ...server, ...global]; // Select first of each type of plugin return distinctByProperty(plugins, p => p.plugin); } diff --git a/packages/openapi-2-kong/src/kubernetes/variables.test.ts b/packages/openapi-2-kong/src/kubernetes/variables.test.ts index aa80b003d..97b446fc7 100644 --- a/packages/openapi-2-kong/src/kubernetes/variables.test.ts +++ b/packages/openapi-2-kong/src/kubernetes/variables.test.ts @@ -1,4 +1,4 @@ -import { OA3Variables } from '../types/openapi3'; +import { OA3ServerVariable } from '../types/openapi3'; import { resolveUrlVariables, resolveVariables, pathVariablesToWildcard } from './variables'; describe('variables', () => { @@ -19,7 +19,7 @@ describe('variables', () => { }); it('should return default in place of variable', () => { - const variables: OA3Variables = { + const variables: Record = { var: { default: 'bar', }, @@ -29,7 +29,7 @@ describe('variables', () => { }); it('should return defaults in place of multiple variables', () => { - const variables: OA3Variables = { + const variables: Record = { var1: { default: 'darkness', }, @@ -54,7 +54,7 @@ describe('variables', () => { it('should replace protocol variable with default protocol', () => { const url = '{protocol}://api.insomnia.rest'; - const variables: OA3Variables = { + const variables: Record = { protocol: { default: 'https', }, @@ -79,7 +79,7 @@ describe('variables', () => { it('should handle protocol and path variables with and without defaults', () => { const url = '{protocol}://api.insomnia.rest/hello/{var}/my/{another-var}/friend/'; - const variables: OA3Variables = { + const variables: Record = { 'another-var': { default: 'old', }, @@ -91,7 +91,7 @@ describe('variables', () => { it('should handle partial routes with variables', () => { const partial = '/hello/{var}/my/{another-var}/friend'; - const variables: OA3Variables = { + const variables: Record = { 'another-var': { default: 'old', }, diff --git a/packages/openapi-2-kong/src/kubernetes/variables.ts b/packages/openapi-2-kong/src/kubernetes/variables.ts index f8fe1d904..ff011441f 100644 --- a/packages/openapi-2-kong/src/kubernetes/variables.ts +++ b/packages/openapi-2-kong/src/kubernetes/variables.ts @@ -1,10 +1,10 @@ -import { OA3Variables } from '../types/openapi3'; +import { OA3ServerVariable } from '../types/openapi3'; const protocolSearchValue = /{([^}]+)}(?=:\/\/)/g; // positive lookahead for :// const pathSearchValue = /{([^}]+)}(?!:\/\/)/g; // negative lookahead for :// -export function resolveUrlVariables(url: string, variables?: OA3Variables): string { +export function resolveUrlVariables(url: string, variables?: Record): string { const protocolResolved = resolveVariables(url, protocolSearchValue, 'http', variables); const pathResolved = resolveVariables(protocolResolved, pathSearchValue, '.*', variables); return pathResolved; @@ -13,7 +13,7 @@ export function resolveVariables( str: string, regExp: RegExp, fallback: string, - variables?: OA3Variables, + variables?: Record, ): string { let resolved = str; let shouldContinue = true; diff --git a/packages/openapi-2-kong/src/types/declarative-config.ts b/packages/openapi-2-kong/src/types/declarative-config.ts index 7da022acf..0c0b17603 100644 --- a/packages/openapi-2-kong/src/types/declarative-config.ts +++ b/packages/openapi-2-kong/src/types/declarative-config.ts @@ -1,54 +1,35 @@ -import { OA3Parameter } from './openapi3'; +import { Plugin } from './kong'; +import { Pluggable, Taggable } from './outputs'; -export interface DCPluginConfig { - allowed_content_types?: string[]; - auth_methods?: string[]; - body_schema?: string; - issuer?: string; - key_names?: string[]; - parameter_schema?: OA3Parameter[] | 'global' | 'path' | 'operation'; - scopes_required?: string[]; - verbose_response?: boolean; - version?: 'draft4'; - [key: string]: any; -} +// In case this seems weird to you, it's an important semantic difference. +// The `Plugin` type is intentionally focused on the _Kong_ plugin definitions without knowledge of declarative-config or kubernetes or any other transforms. +// It happens to be the case, yes, that the DC shape directly matches the Kong plugin shape in all cases, but this is by coincidence, as far as the types are concerned. Other transformers, e.g. Kubernetes, use this same `Plugin` type but resituate it quite a bit. It just so happens that declarative config does not do this (yet). +// Finally, if nothing else it serves as useful documentation to distinguish when we're talking about declarative-config vs kong-config. +export type DCPlugin = Plugin -export interface DCPlugin { - name: string; - enabled?: boolean; - tags?: string[]; - config?: DCPluginConfig; -} - -export interface DCRoute { +export interface DCRoute extends Taggable, Pluggable { methods: string[]; // eslint-disable-next-line camelcase -- this is defined by a spec that is out of our control - strip_path: boolean; - tags: string[]; name: string; paths: string[]; - plugins?: DCPlugin[]; + strip_path: boolean; } -export interface DCService { +export interface DCService extends Taggable, Pluggable { + host: DCUpstream['name']; name: string; - protocol: string | undefined; - host: string; - port: number; path: string | null; + port: number; + protocol: string | undefined; routes: DCRoute[]; - tags: string[]; - plugins?: DCPlugin[]; } -export interface DCTarget { +export interface DCTarget extends Taggable { target: string; - tags?: string[]; } -export interface DCUpstream { +export interface DCUpstream extends Taggable { name: string; - tags: string[]; targets: DCTarget[]; } diff --git a/packages/openapi-2-kong/src/types/index.ts b/packages/openapi-2-kong/src/types/index.ts index 058f66f9e..955e9af6c 100644 --- a/packages/openapi-2-kong/src/types/index.ts +++ b/packages/openapi-2-kong/src/types/index.ts @@ -1,5 +1,5 @@ export * from './declarative-config'; -export * from './k8splugins'; +export * from './k8s-plugins'; export * from './kubernetes-config'; export * from './openapi3'; export * from './outputs'; diff --git a/packages/openapi-2-kong/src/types/k8splugins.ts b/packages/openapi-2-kong/src/types/k8s-plugins.ts similarity index 100% rename from packages/openapi-2-kong/src/types/k8splugins.ts rename to packages/openapi-2-kong/src/types/k8s-plugins.ts diff --git a/packages/openapi-2-kong/src/types/kong.ts b/packages/openapi-2-kong/src/types/kong.ts new file mode 100644 index 000000000..643f06510 --- /dev/null +++ b/packages/openapi-2-kong/src/types/kong.ts @@ -0,0 +1,311 @@ +import { RequireAtLeastOne } from 'type-fest'; +import { DCRoute, DCService, DCUpstream } from './declarative-config'; +import { Taggable } from './outputs'; + +export type XKongProperty = `x-kong-${Property}`; + +export const xKongName: XKongProperty<'name'> = 'x-kong-name'; +export interface XKongName { + [xKongName]?: string; +} + +export const xKongRouteDefaults: XKongProperty<'route-defaults'> = 'x-kong-route-defaults'; +export interface XKongRouteDefaults { + [xKongRouteDefaults]?: Partial; +} + +export const xKongUpstreamDefaults: XKongProperty<'upstream-defaults'> = 'x-kong-upstream-defaults'; +export interface XKongUpstreamDefaults { + [xKongUpstreamDefaults]?: Partial; +} + +export const xKongServiceDefaults: XKongProperty<'service-defaults'> = 'x-kong-service-defaults'; +export interface XKongServiceDefaults { + [xKongServiceDefaults]?: Partial; +} + +export type XKongPluginProperty = XKongProperty<`plugin-${Name}`> + +// Note: it's important that `Name` doesn't have a default argument. We want to force the consumer of this type to specify the name as specifically as possible because this value is instrumental to how the plugins are used and discriminated. +export interface PluginBase extends Taggable { + /** + * Whether this plugin will be applied. + * + * @defaultValue true + */ + enabled?: boolean; + /** The name of the plugin to use. */ + name: Name; + config?: Record; + service?: { + /** The ID of the Service the plugin targets. */ + id: string; + }; + route?: { + /** The ID of the Route the plugin targets. */ + id: string; + }; + consumer?: { + /** The ID of the Consumer the plugin targets. */ + id: string; + }; +} + +/** + * A plugin which is not associated to any service, route, or consumer is considered global, and will be run on every request. + * + * see: https://docs.konghq.com/hub/kong-inc/openid-connect/#enabling-the-plugin-globally + */ +export type GlobalPluginBase = Omit, 'service' | 'route' | 'consumer'> + +/** used for user-defined or yet-untyped plugins */ +export type XKongPlugin> = Partial< + Record< + XKongPluginProperty, + Plugin + > +> + +export interface BodySchema { + /** The request body schema specification */ + body_schema: string; +} + +export interface ParameterSchemaRequired { + /** + * The name of the parameter. Parameter names are case sensitive, and corresponds to the parameter name used by the in property. + * + * If in is "path", the name field MUST correspond to the named capture group from the configured route. + */ + name: string; + /** + * The location of the parameter. Possible values are query, header, or path. + */ + in: string; + /** + * Determines whether this parameter is mandatory. + */ + required: boolean; +} + +// These properties are optional but if any one of them is set, the others must also be set. +export interface ParameterSchemaOptional { + /** + * Describes how the parameter value will be serialized depending on the type of the parameter value + */ + style: string; + /** + * The schema defining the type used for the parameter. + * + * It is validated using draft4 for JSONschema draft 4 compliant validator. + */ + schema: string; + /** + * When this is true, parameter values of type array or object generate separate parameters for each value of the array or key-value pair of the map. + * + * For other types of parameters this property has no effect. +Permalink + */ + explode: boolean; +} + +/** see: https://docs.konghq.com/hub/kong-inc/request-validator/#parameter-schema-definition */ +export type ParameterSchema = ParameterSchemaRequired | (ParameterSchemaRequired & ParameterSchemaOptional) + +export interface ParameterSchemas { + /** + * Array of parameter validator specifications. For details and examples, see Parameter Schema Definition https://docs.konghq.com/hub/kong-inc/request-validator/#parameter-schema-definition. + */ + parameter_schema: ParameterSchema[]; +} + +export const isBodySchema = (value: Partial = {}): value is BodySchema => ( + Object.prototype.hasOwnProperty.call(value, 'body_schema') && (value as BodySchema).body_schema !== undefined +); + +export const isParameterSchema = (value: Partial = {}): value is ParameterSchemas => ( + Object.prototype.hasOwnProperty.call(value, 'parameter_schema') && (value as ParameterSchemas).parameter_schema !== undefined +); + +/** see: https://docs.konghq.com/hub/kong-inc/request-validator/#parameters */ +export type RequestValidator = 'request-validator'; +export interface RequestValidatorPlugin extends PluginBase { + config: { + /** + * List of allowed content types. + * + * Note: Body validation is only done for application/json and skipped for any other allowed content types. + * + * @defaultValue application/json + */ + allowed_content_types?: string[]; + /** + * Which validator to use. + * + * Supported values are kong (default) for using Kong’s own schema validator, or draft4 for using a JSON Schema Draft 4-compliant validator. + * + * @defaultValue kong + */ + version?: 'draft4' | 'kong'; + /** + * If enabled, the plugin returns more verbose and detailed validation errors (for example, the name of the required field that is missing). + * + * @defaultValue false + */ + verbose_response?: boolean; + } & RequireAtLeastOne; +} +export const xKongPluginRequestValidator: XKongPluginProperty = 'x-kong-plugin-request-validator'; +export type XKongPluginRequestValidator = XKongPlugin + +/** see: https://docs.konghq.com/hub/kong-inc/key-auth/#parameters */ +export type KeyAuth = 'key-auth'; +export interface KeyAuthPlugin extends PluginBase { + config: { + /** + * Describes an array of parameter names where the plugin will look for a key. + * + * The client must send the authentication key in one of those key names, and the plugin will try to read the credential from a header, request body, or query string parameter with the same name. + * + * Note: The key names may only contain [a-z], [A-Z], [0-9], [_] underscore, and [-] hyphen. + * + * @defaultValue apikey + */ + key_names: string[]; + /** + * If enabled, the plugin reads the request body (if said request has one and its MIME type is supported) and tries to find the key in it. + * + * Supported MIME types: application/www-form-urlencoded, application/json, and multipart/form-data. + * + * @defaultValue false + */ + key_in_body?: boolean; + /** + * If enabled (default), the plugin reads the request header and tries to find the key in it. + * + * @defaultValue true + */ + key_in_header?: boolean; + /** + * If enabled (default), the plugin reads the query parameter in the request and tries to find the key in it. + * + * @defaultValue true + */ + key_in_query?: boolean; + /** + * An optional boolean value telling the plugin to show or hide the credential from the upstream service. + * + * If true, the plugin strips the credential from the request (i.e., the header, query string, or request body containing the key) before proxying it. + * + * @defaultValue false + */ + hide_credentials?: boolean; + /** + * An optional string (Consumer UUID) value to use as an anonymous Consumer if authentication fails. + * + * If empty (default), the request will fail with an authentication failure 4xx. + * + * Note that this value must refer to the Consumer id attribute that is internal to Kong, and not its custom_id. + */ + anonymous?: string; + /** + * A boolean value that indicates whether the plugin should run (and try to authenticate) on OPTIONS preflight requests. + * + * If set to false, then OPTIONS requests are always allowed. + * + * @defaultValue true + */ + run_on_preflight?: boolean; + }; +} +export const xKongPluginKeyAuth: XKongPluginProperty = 'x-kong-plugin-key-auth'; +export type XKongPluginKeyAuth = XKongPlugin; + +/** see: https://docs.konghq.com/hub/kong-inc/request-termination/#parameters */ +export type RequestTermination = 'request-termination'; +export interface RequestTerminationPlugin extends PluginBase { + config?: { + /** + * the response code to send + * + * must be an integer between 100 and 599 */ + status_code?: number; + /** the message to send, if using the default response generator */ + message?: string; + /** + * the raw response body to send + * + * this is mutually exclusive with the config.message field */ + body?: string; + /** + * content type of the raw response configured with `config.body` + * + * @defaultValue application/json; charset=utf-8 + */ + content_type?: string; + } +} +export const xKongPluginRequestTermination: XKongPluginProperty = 'x-kong-plugin-request-termination'; +export type XKongPluginRequestTermination = XKongPlugin + +/** see: https://docs.konghq.com/hub/kong-inc/basic-auth/#parameters */ +export type BasicAuth = 'basic-auth'; +export interface BasicAuthPlugin extends Omit, 'consumer'> { + config?: { + /** + * An optional boolean value telling the plugin to show or hide the credential from the upstream service. + * + * If true, the plugin will strip the credential from the request (i.e. the Authorization header) before proxying it. + * + * @defaultValue false + */ + hide_credentials?: boolean; + /** + * An optional string (consumer uuid) value to use as an “anonymous” consumer if authentication fails. + * + * If empty (default), the request will fail with an authentication failure 4xx. + * + * Please note that this value must refer to the Consumer id attribute which is internal to Kong, and not its custom_id. + */ + anonymous?: string; + } +} +export const xKongBasicAuth: XKongPluginProperty = 'x-kong-plugin-basic-auth'; +export type XKongBasicAuthPlugin = XKongPlugin + +export type AuthMethod = + | 'password' + | 'client_credentials' + | 'authorization_code' + | 'bearer' + | 'introspection' + | 'kong_oauth2' + | 'refresh_token' + | 'session' + +export type OpenIDConnect = 'openid-connect'; + +/** + * Note: These types are incomplete, as there are dozens of parameters for the config. + * + * Note: This is a global plugin, not associated to any service, route, or consumer. + * + * see: https://docs.konghq.com/hub/kong-inc/openid-connect/#parameter-descriptions + */ +export interface OpenIDConnectPlugin extends GlobalPluginBase { + enabled?: boolean; + config?: { + issuer?: string; + scopes_required?: string[]; + auth_methods?: AuthMethod[]; + } +} +export const xKongOpenIDConnect: XKongPluginProperty = 'x-kong-plugin-openid-connect'; +export type XOpenIDConnectPlugin = XKongPlugin; + +export type Plugin = + | RequestValidatorPlugin + | KeyAuthPlugin + | RequestTerminationPlugin + | BasicAuthPlugin + | OpenIDConnectPlugin; diff --git a/packages/openapi-2-kong/src/types/kubernetes-config.ts b/packages/openapi-2-kong/src/types/kubernetes-config.ts index d4e1d0ac5..75a43fa4a 100644 --- a/packages/openapi-2-kong/src/types/kubernetes-config.ts +++ b/packages/openapi-2-kong/src/types/kubernetes-config.ts @@ -1,4 +1,5 @@ import { HttpMethodType } from '../common'; +import { Plugin, PluginBase } from './kong'; export interface K8sIngressClassAnnotation { 'kubernetes.io/ingress.class'?: 'kong'; @@ -22,8 +23,9 @@ export type K8sAnnotations = /** see: https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.21/#objectmeta-v1-meta */ export interface K8sMetadata { + /** The unique-per-instance name used by kubernetes to track individual Kubernetes resources */ name: string; - annotations: K8sAnnotations; + annotations?: K8sAnnotations; } /** see: https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.21/#ingressbackend-v1beta1-extensions */ @@ -62,38 +64,41 @@ export interface K8sIngressSpec { tls?: K8sIngressTLS[]; } +export interface KubernetesResource { + apiVersion: string; + kind: string; + metadata: K8sMetadata, +} + /** see: https://docs.konghq.com/kubernetes-ingress-controller/1.2.x/concepts/custom-resources/#kongingress */ -export interface K8sKongIngress { +export interface K8sKongIngress extends KubernetesResource { apiVersion: 'configuration.konghq.com/v1'; kind: 'KongIngress'; - metadata: { - name: string; - }; route: { methods: (HttpMethodType | Lowercase)[]; }; } -/** see: https://docs.konghq.com/kubernetes-ingress-controller/1.2.x/concepts/custom-resources/#kongplugin */ -export interface K8sKongPlugin { - apiVersion: 'configuration.konghq.com/v1'; - kind: 'KongPlugin'; - metadata: { - name: string; - global?: boolean; - }; - config?: Record; - plugin: string; -} - /** see: https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.21/#ingress-v1beta1-extensions */ -export interface K8sIngress { +export interface K8sIngress extends KubernetesResource { apiVersion: 'extensions/v1beta1'; kind: 'Ingress'; - metadata: K8sMetadata; spec: K8sIngressSpec; } +/** see: https://docs.konghq.com/kubernetes-ingress-controller/1.2.x/concepts/custom-resources/#kongplugin */ +export interface K8sKongPluginBase> extends KubernetesResource { + apiVersion: 'configuration.konghq.com/v1'; + kind: 'KongPlugin'; + metadata: K8sMetadata & { + global?: boolean; + }; + config?: Plugin['config']; + plugin: Plugin['name']; +} + +export type K8sKongPlugin = K8sKongPluginBase + export type K8sManifest = | K8sIngress | K8sKongIngress diff --git a/packages/openapi-2-kong/src/types/openapi3.ts b/packages/openapi-2-kong/src/types/openapi3.ts index 133c7f711..0e794a7a5 100644 --- a/packages/openapi-2-kong/src/types/openapi3.ts +++ b/packages/openapi-2-kong/src/types/openapi3.ts @@ -1,67 +1,15 @@ -import { DCRoute, DCUpstream } from './declarative-config'; +import { HttpMethodType } from '../common'; import { K8sIngressTLS } from './kubernetes-config'; - -export const xKongName = 'x-kong-name'; -export interface XKongName { - [xKongName]?: string; -} - -export const xKongRouteDefaults = 'x-kong-route-defaults'; -export interface XKongRouteDefaults { - [xKongRouteDefaults]?: Partial; -} - -export const xKongUpstreamDefaults = 'x-kong-upstream-defaults'; -export interface XKongUpstreamDefaults { - [xKongUpstreamDefaults]?: Partial; -} - -export const xKongServiceDefaults = 'x-kong-service-defaults'; -export interface XKongServiceDefaults { - [xKongServiceDefaults]?: Record; -} - -export const xKongPluginRequestValidator = 'x-kong-plugin-request-validator'; -export interface XKongPluginRequestValidator { - [xKongPluginRequestValidator]?: { - enabled?: boolean; - config: { - verbose_response?: boolean; - parameter_schema?: 'global' | 'path' | 'operation'; - }; - }; -} - -export const xKongPluginKeyAuth = 'x-kong-plugin-key-auth'; -export interface XKongPluginKeyAuth { - [xKongPluginKeyAuth]?: { - name: 'key-auth'; - config: { - key_names: string[]; - key_in_body?: boolean; - hide_credentials?: boolean; - }; - }; -} - -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; -}> +import { Taggable } from './outputs'; +import { + XKongName, + XKongPluginKeyAuth, + XKongPluginRequestTermination, + XKongPluginRequestValidator, + XKongRouteDefaults, + XKongServiceDefaults, + XKongUpstreamDefaults, +} from './kong'; export interface StripPath { // eslint-disable-next-line camelcase -- this is defined by a spec that is out of our control @@ -106,12 +54,16 @@ export interface OA3Parameter { explode?: boolean; } +/** see: https://swagger.io/specification/#request-body-object */ export interface OA3RequestBody { content?: Record; // TODO + description?: string; + required?: boolean; } export type OA3SecurityRequirement = Record; +/** see: https://swagger.io/specification/#reference-object */ export interface OA3Reference { $ref: string; } @@ -140,16 +92,18 @@ export interface OA3ServerKubernetesService { }; } -export type OA3Variables = Record; +} +/** see: https://swagger.io/specification/#server-object */ export type OA3Server = { url: string; description?: string; - variables?: OA3Variables; + variables?: Record; } & OA3ServerKubernetesTLS & OA3ServerKubernetesBackend & OA3ServerKubernetesService; @@ -158,10 +112,10 @@ export interface OA3ResponsesObject { $ref?: string; } +/** see: https://swagger.io/specification/#operation-object */ export type OA3Operation = { description?: string; summary?: string; - tags?: string[]; externalDocs?: OA3ExternalDocs; responses?: OA3ResponsesObject; operationId?: string; @@ -170,36 +124,39 @@ export type OA3Operation = { deprecated?: boolean; security?: OA3SecurityRequirement[]; servers?: OA3Server[]; -} & XKongName +} & Taggable + & XKongName & XKongRouteDefaults & XKongPluginKeyAuth & XKongPluginRequestValidator ; +type HTTPMethodPaths = Partial, + OA3Operation +>>; + +/** see: https://swagger.io/specification/#path-item-object */ export type OA3PathItem = { $ref?: string; summary?: string; description?: string; servers?: OA3Server[]; parameters?: OA3Reference | OA3Parameter; - get?: OA3Operation; - put?: OA3Operation; - post?: OA3Operation; - delete?: OA3Operation; - options?: OA3Operation; - head?: OA3Operation; - patch?: OA3Operation; - trace?: OA3Operation; -} & XKongName +} & HTTPMethodPaths + & XKongName & XKongRouteDefaults & XKongPluginRequestValidator + & XKongPluginKeyAuth ; +/** see: https://swagger.io/specification/#paths-object */ export type OA3Paths = Record & StripPath & XKongRouteDefaults ; +/** see: https://swagger.io/specification/#security-scheme-object */ export interface OA3SecuritySchemeApiKey { type: 'apiKey'; name: string; @@ -207,6 +164,7 @@ export interface OA3SecuritySchemeApiKey { description?: string; } +/** see: https://swagger.io/specification/#security-scheme-object */ export interface OA3SecuritySchemeHttp { type: 'http'; name: string; @@ -215,6 +173,7 @@ export interface OA3SecuritySchemeHttp { description?: string; } +/** see: https://swagger.io/specification/#security-scheme-object */ export interface OA3SecuritySchemeOpenIdConnect { type: 'openIdConnect'; name: string; @@ -222,6 +181,7 @@ export interface OA3SecuritySchemeOpenIdConnect { description?: string; } +/** see: https://swagger.io/specification/#security-scheme-object */ export interface OA3SecuritySchemeOAuth2Flow { authorizationUrl?: string; tokenUrl?: string; @@ -229,6 +189,7 @@ export interface OA3SecuritySchemeOAuth2Flow { scopes: Record; } +/** see: https://swagger.io/specification/#security-scheme-object */ export interface OA3SecuritySchemeOAuth2 { type: 'oauth2'; name: string; @@ -241,16 +202,25 @@ export interface OA3SecuritySchemeOAuth2 { description?: string; } +/** see: https://swagger.io/specification/#security-scheme-object */ export type OA3SecurityScheme = | OA3SecuritySchemeApiKey | OA3SecuritySchemeHttp | OA3SecuritySchemeOpenIdConnect | OA3SecuritySchemeOAuth2; -export interface OA3Example {} +/** see: https://swagger.io/specification/#example-object */ +export interface OA3Example { + summary?: string; + description?: string; + value?: any; + externalValue?: string; +} +/** see: https://swagger.io/specification/#schema-object */ export interface OA3Schema {} +/** see: https://swagger.io/specification/#header-object */ export interface OA3Header { description?: string; required?: boolean; @@ -258,6 +228,7 @@ export interface OA3Header { allowEmptyValue?: boolean; } +/** see: https://swagger.io/specification/#components-object */ export interface OA3Components { schemas?: Record; parameters?: Record; @@ -267,6 +238,14 @@ export interface OA3Components { securitySchemes?: Record; } +/** see: https://swagger.io/specification/#tag-object */ +export interface TagObject { + name: string; + description?: string; + externalDocs?: Record; +} + +/** see: https://swagger.io/specification/#openapi-object */ export type OpenApi3Spec = { openapi: string; info: OA3Info; @@ -274,15 +253,14 @@ export type OpenApi3Spec = { servers?: OA3Server[]; components?: OA3Components; security?: OA3SecurityRequirement[]; - tags?: string[]; externalDocs?: OA3ExternalDocs; + tags?: TagObject[]; } + & XKongName & XKongPluginKeyAuth & XKongPluginRequestTermination & XKongPluginRequestValidator - & XKongPluginUnknown & XKongRouteDefaults & XKongServiceDefaults & XKongUpstreamDefaults - & XKongName ; diff --git a/packages/openapi-2-kong/src/types/outputs.ts b/packages/openapi-2-kong/src/types/outputs.ts index d0dd511d5..411aa61d5 100644 --- a/packages/openapi-2-kong/src/types/outputs.ts +++ b/packages/openapi-2-kong/src/types/outputs.ts @@ -1,6 +1,14 @@ -import { DeclarativeConfig } from './declarative-config'; +import { DCPlugin, DeclarativeConfig } from './declarative-config'; import { K8sManifest } from './kubernetes-config'; +export interface Taggable { + tags?: string[]; +} + +export interface Pluggable { + plugins?: DCPlugin[]; +} + export type ConversionResultType = 'kong-declarative-config' | 'kong-for-kubernetes'; export interface Warning { diff --git a/packages/openapi-2-kong/tsconfig.build.json b/packages/openapi-2-kong/tsconfig.build.json index dfdf82498..29d7a4a2f 100644 --- a/packages/openapi-2-kong/tsconfig.build.json +++ b/packages/openapi-2-kong/tsconfig.build.json @@ -11,6 +11,7 @@ "src" ], "exclude": [ - "**/*.test.ts" + "**/*.test.ts", + "jest" ] } \ No newline at end of file From f72fca6a7b0fab7d2f525fdb7be620e7d776a931 Mon Sep 17 00:00:00 2001 From: Eric Reynolds Date: Wed, 2 Jun 2021 08:47:23 -0700 Subject: [PATCH 05/20] Margin control to insomnia-component styled buttons (#3429) --- .../grpc-method-dropdown-button.tsx | 11 +++++++++-- packages/insomnia-components/src/button/button.tsx | 2 ++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/packages/insomnia-app/app/ui/components/dropdowns/grpc-method-dropdown/grpc-method-dropdown-button.tsx b/packages/insomnia-app/app/ui/components/dropdowns/grpc-method-dropdown/grpc-method-dropdown-button.tsx index d6be2dfbe..fe4bdcf50 100644 --- a/packages/insomnia-app/app/ui/components/dropdowns/grpc-method-dropdown/grpc-method-dropdown-button.tsx +++ b/packages/insomnia-app/app/ui/components/dropdowns/grpc-method-dropdown/grpc-method-dropdown-button.tsx @@ -1,7 +1,7 @@ import React, { FunctionComponent, useMemo } from 'react'; import styled from 'styled-components'; import { getGrpcPathSegments, getShortGrpcPath } from '../../../../common/grpc-paths'; -import { Button, Tooltip } from 'insomnia-components'; +import { Button, ButtonProps, Tooltip } from 'insomnia-components'; const FlexSpaceBetween = styled.span` width: 100%; @@ -25,8 +25,15 @@ const useLabel = (fullPath?: string) => return 'Select Method'; }, [fullPath]); +const buttonProps: ButtonProps = { + className: 'tall wide', + variant: 'text', + size: 'medium', + radius: '0', +}; + const GrpcMethodDropdownButton: FunctionComponent = ({ fullPath }) => ( -