Skip to content

Commit

Permalink
feat(apidom-converter): add securitySchemeTypeRefractorPlugin (#3802)
Browse files Browse the repository at this point in the history
  • Loading branch information
kowalczyk-krzysztof committed Feb 12, 2024
1 parent ec8fa05 commit 2666194
Show file tree
Hide file tree
Showing 5 changed files with 254 additions and 1 deletion.
Expand Up @@ -18,6 +18,7 @@ import {
import ConvertStrategy, { IFile } from '../ConvertStrategy';
import openAPIVersionRefractorPlugin from './refractor-plugins/openapi-version';
import webhooksRefractorPlugin from './refractor-plugins/webhooks';
import securitySchemeTypeRefractorPlugin from './refractor-plugins/security-scheme-type';
import type { ConverterOptions } from '../../options';
import createToolbox from './toolbox';

Expand Down Expand Up @@ -56,7 +57,11 @@ class OpenAPI31ToOpenAPI30ConvertStrategy extends ConvertStrategy {
const annotations: AnnotationElement[] = [];
const parseResultElement = dispatchRefractorPlugins(
cloneDeep(file.parseResult),
[openAPIVersionRefractorPlugin(), webhooksRefractorPlugin({ annotations })],
[
openAPIVersionRefractorPlugin(),
webhooksRefractorPlugin({ annotations }),
securitySchemeTypeRefractorPlugin({ annotations }),
],
{
toolboxCreator: createToolbox,
visitorOptions: { keyMap, nodeTypeGetter: getNodeType },
Expand Down
@@ -0,0 +1,87 @@
import {
ComponentsElement,
OpenApi3_1Element,
SecurityRequirementElement,
SecuritySchemeElement,
isSecuritySchemeElement,
isComponentsElement,
} from '@swagger-api/apidom-ns-openapi-3-1';
import { AnnotationElement, toValue, isObjectElement, Element } from '@swagger-api/apidom-core';

import type { Toolbox } from '../toolbox';

type SecuritySchemeTypePluginOptions = {
annotations: AnnotationElement[];
};

const securitySchemeTypeRefractorPlugin =
({ annotations }: SecuritySchemeTypePluginOptions) =>
(toolbox: Toolbox) => {
const removedSecuritySchemes: SecuritySchemeElement[] = [];
const createAnnotation = <T extends Element>(element: T) =>
toolbox.createAnnotation.fromElement(
element,
'The "mutualTLS" type Security Scheme Object is not supported in OpenAPI 3.0.3. As a result, all Security Scheme Objects specified with the "mutualTLS" type have been removed.',
{ classes: ['error'] },
{ code: 'mutualTLS' },
);

return {
visitor: {
OpenApi3_1Element(element: OpenApi3_1Element) {
if (!isComponentsElement(element.components)) return undefined;
if (!isObjectElement(element.components.securitySchemes)) return undefined;

element.components.securitySchemes.forEach((value) => {
if (isSecuritySchemeElement(value) && toValue(value.type) === 'mutualTLS') {
removedSecuritySchemes.push(value);
}
});

return undefined;
},
ComponentsElement(element: ComponentsElement) {
if (!isObjectElement(element.securitySchemes)) return undefined;

element.securitySchemes.forEach((value, key) => {
if (isSecuritySchemeElement(value) && toValue(value.type) === 'mutualTLS') {
if (!removedSecuritySchemes.includes(value)) removedSecuritySchemes.push(value);
(element.securitySchemes as SecuritySchemeElement).remove(toValue(key));
annotations.push(createAnnotation(value));
}
});

return undefined;
},
SecurityRequirementElement(element: SecurityRequirementElement) {
if (!removedSecuritySchemes.length) return undefined;

const keysToRemove: string[] = [];

element.forEach((value, key) => {
const removedSecurityScheme = removedSecuritySchemes.find(
(securityScheme) => toValue(securityScheme.name) === toValue(key),
);

if (isSecuritySchemeElement(removedSecurityScheme)) {
keysToRemove.push(toValue(key));
annotations.push(createAnnotation(value));
}
});

if (!keysToRemove.length) return undefined;

keysToRemove.forEach((key) => {
element.remove(key);
});

return undefined;
},
},
post() {
removedSecuritySchemes.length = 0;
},
};
};

export default securitySchemeTypeRefractorPlugin;
@@ -0,0 +1,38 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`converter strategies openapi-3-1-to-openapi-3-0-3 security-scheme-type should remove SecurityScheme object if it has "mutualTLS" type 1`] = `
{
"openapi": "3.0.3",
"paths": {
"/foo": {
"get": {
"summary": "foo",
"operationId": "foo",
"security": [
{},
{
"apiKey-scheme": [
"read",
"write"
]
}
],
"responses": {
"200": {
"description": "foo"
}
}
}
}
},
"components": {
"securitySchemes": {
"apiKey-scheme": {
"type": "apiKey",
"name": "apiKey-scheme",
"in": "header"
}
}
}
}
`;
@@ -0,0 +1,44 @@
{
"openapi": "3.1.0",
"paths": {
"/foo": {
"get": {
"summary": "foo",
"operationId": "foo",
"security": [
{
"mutualTLS-scheme": [
"read",
"write"
]
},
{
"apiKey-scheme": [
"read",
"write"
]
}
],
"responses": {
"200": {
"description": "foo"
}
}
}
}
},
"components": {
"securitySchemes": {
"mutualTLS-scheme": {
"type": "mutualTLS",
"name": "mutualTLS-scheme",
"in": "header"
},
"apiKey-scheme": {
"type": "apiKey",
"name": "apiKey-scheme",
"in": "header"
}
}
}
}
@@ -0,0 +1,79 @@
import path from 'node:path';
import { mediaTypes as openAPI31MediaTypes } from '@swagger-api/apidom-parser-adapter-openapi-json-3-1';
import { mediaTypes as openAPI30MediaTypes } from '@swagger-api/apidom-parser-adapter-openapi-json-3-0';
import { AnnotationElement, includesClasses, toJSON, toValue } from '@swagger-api/apidom-core';
import { assert, expect } from 'chai';

import convert from '../../../../../src';

describe('converter', function () {
context('strategies', function () {
context('openapi-3-1-to-openapi-3-0-3', function () {
context('security-scheme-type', function () {
specify(
'should remove SecurityScheme object if it has "mutualTLS" type',
async function () {
const fixturePath = path.join(__dirname, 'fixtures', 'security-scheme-type.json');
const convertedParseResult = await convert(fixturePath, {
convert: {
sourceMediaType: openAPI31MediaTypes.findBy('3.1.0', 'json'),
targetMediaType: openAPI30MediaTypes.findBy('3.0.3', 'json'),
},
});

expect(toJSON(convertedParseResult.api!, undefined, 2)).toMatchSnapshot();
},
);

specify('should create ERROR annotation', async function () {
const fixturePath = path.join(__dirname, 'fixtures', 'security-scheme-type.json');
const convertedParseResult = await convert(fixturePath, {
convert: {
sourceMediaType: openAPI31MediaTypes.findBy('3.1.0', 'json'),
targetMediaType: openAPI30MediaTypes.findBy('3.0.3', 'json'),
},
});
const annotations = Array.from(convertedParseResult.annotations);
const annotation = annotations.find((a: AnnotationElement) =>
a.code?.equals('mutualTLS'),
);

assert.isDefined(annotation);
assert.lengthOf(annotations, 2);
assert.isTrue(includesClasses(['error'], annotation));
});

specify('should attach source map to annotation', async function () {
const fixturePath = path.join(__dirname, 'fixtures', 'security-scheme-type.json');
const convertedParseResult = await convert(fixturePath, {
parse: {
parserOpts: { sourceMap: true },
},
convert: {
sourceMediaType: openAPI31MediaTypes.findBy('3.1.0', 'json'),
targetMediaType: openAPI30MediaTypes.findBy('3.0.3', 'json'),
},
});
const annotations = Array.from(convertedParseResult.annotations);
const annotation: AnnotationElement = annotations.find((a: AnnotationElement) =>
a.code?.equals('mutualTLS'),
);
const sourceMap = annotation.meta.get('sourceMap');
const { positionStart, positionEnd } = sourceMap;
const [startRow, startColumn, startChar] = toValue(positionStart);
const [endRow, endColumn, endChar] = toValue(positionEnd);

assert.isDefined(sourceMap);

assert.strictEqual(startRow, 9);
assert.strictEqual(startColumn, 32);
assert.strictEqual(startChar, 188);

assert.strictEqual(endRow, 12);
assert.strictEqual(endColumn, 13);
assert.strictEqual(endChar, 247);
});
});
});
});
});

0 comments on commit 2666194

Please sign in to comment.