Skip to content

Commit

Permalink
feat(ns-openapi-3-1): make operation ids refractor plugin idempotent
Browse files Browse the repository at this point in the history
Refs #4134
  • Loading branch information
glowcloud committed May 28, 2024
1 parent e28f053 commit 99f83e3
Show file tree
Hide file tree
Showing 5 changed files with 358 additions and 6 deletions.
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { last, defaultTo, groupBy } from 'ramda';
import { toValue, StringElement, Namespace, cloneDeep } from '@swagger-api/apidom-core';
import { toValue, Element, StringElement, cloneDeep } from '@swagger-api/apidom-core';

import LinkElement from '../../elements/Link';
import PathItemElement from '../../elements/PathItem';
import OperationElement from '../../elements/Operation';
import { Predicates } from '../toolbox';
import type { Toolbox } from '../toolbox';
import OpenApi3_1Element from '../../elements/OpenApi3-1';
import NormalizeStorage from './normalize-header-examples/NormalizeStorage';

const removeSpaces = (operationId: string) => {
return operationId.replace(/\s/g, '');
Expand Down Expand Up @@ -45,19 +47,33 @@ const normalizeOperationId = (operationId: string, path: string, method: string)
* This plugin also guarantees the uniqueness of all defined Operation.operationId fields,
* and make sure Link.operationId fields are pointing to correct and normalized Operation.operationId fields.
*
* NOTE: this plugin is idempotent
*/
/* eslint-disable no-param-reassign */

interface PluginOptions {
storageField?: string;
operationIdNormalizer?: (operationId: string, path: string, method: string) => string;
}

/* eslint-disable no-param-reassign */
const plugin =
({ operationIdNormalizer = normalizeOperationId } = {}) =>
({ predicates, namespace }: { predicates: Predicates; namespace: Namespace }) => {
({
storageField = 'x-normalized',
operationIdNormalizer = normalizeOperationId,
}: PluginOptions = {}) =>
(toolbox: Toolbox) => {
const { predicates, ancestorLineageToJSONPointer, namespace } = toolbox;
const paths: string[] = [];
const normalizedOperations: OperationElement[] = [];
const links: LinkElement[] = [];
let storage: NormalizeStorage | undefined;

return {
visitor: {
OpenApi3_1Element: {
enter(element: OpenApi3_1Element) {
storage = new NormalizeStorage(element, storageField, 'operation-ids');
},
leave() {
// group normalized operations by normalized operationId
const normalizedOperationGroups = groupBy((operationElement: OperationElement) => {
Expand Down Expand Up @@ -103,6 +119,7 @@ const plugin =
// cleanup the references
normalizedOperations.length = 0;
links.length = 0;
storage = undefined;
},
},
PathItemElement: {
Expand All @@ -116,10 +133,27 @@ const plugin =
},
},
OperationElement: {
enter(operationElement: OperationElement) {
enter(
operationElement: OperationElement,
key: string | number,
parent: Element | undefined,
elementPath: (string | number)[],
ancestors: [Element | Element[]],
) {
// operationId field is undefined, needs no normalization
if (typeof operationElement.operationId === 'undefined') return;

const operationJSONPointer = ancestorLineageToJSONPointer([
...ancestors,
parent!,
operationElement,
]);

// skip visiting this Operation Object if it's already normalized
if (storage!.includes(operationJSONPointer)) {
return;
}

// cast operationId to string type
const originalOperationId = String(toValue(operationElement.operationId));
// perform operationId normalization
Expand All @@ -140,6 +174,7 @@ const plugin =
operationElement.meta.set('originalOperationId', originalOperationId);

normalizedOperations.push(operationElement);
storage!.append(operationJSONPointer);
},
},
LinkElement: {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`refractor plugins normalize-parameters should have idempotent characteristics 1`] = `
Object {
components: Object {
links: Object {
link1: Object {
operationId: get operation ^,
},
},
paths: Object {
/: Object {
get: Object {
operationId: get operation ^,
},
},
},
},
openapi: 3.1.0,
}
`;
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,32 @@ exports[`refractor plugins normalize-operation-ids given Operation Object with n
]
}
}
},
{
"element": "member",
"content": {
"key": {
"element": "string",
"content": "x-normalized"
},
"value": {
"element": "object",
"content": [
{
"element": "member",
"content": {
"key": {
"element": "string",
"content": "operation-ids"
},
"value": {
"element": "array"
}
}
}
]
}
}
}
]
}
Expand Down Expand Up @@ -595,6 +621,38 @@ exports[`refractor plugins normalize-operation-ids given Operation Object with o
]
}
}
},
{
"element": "member",
"content": {
"key": {
"element": "string",
"content": "x-normalized"
},
"value": {
"element": "object",
"content": [
{
"element": "member",
"content": {
"key": {
"element": "string",
"content": "operation-ids"
},
"value": {
"element": "array",
"content": [
{
"element": "string",
"content": "/paths/~1path~1to~1resource/get"
}
]
}
}
}
]
}
}
}
]
}
Expand Down Expand Up @@ -815,6 +873,38 @@ exports[`refractor plugins normalize-operation-ids given Operation Object with o
]
}
}
},
{
"element": "member",
"content": {
"key": {
"element": "string",
"content": "x-normalized"
},
"value": {
"element": "object",
"content": [
{
"element": "member",
"content": {
"key": {
"element": "string",
"content": "operation-ids"
},
"value": {
"element": "array",
"content": [
{
"element": "string",
"content": "/paths/~1path~1to~1resource/get"
}
]
}
}
}
]
}
}
}
]
}
Expand Down Expand Up @@ -1173,6 +1263,38 @@ exports[`refractor plugins normalize-operation-ids given Operation Object with u
]
}
}
},
{
"element": "member",
"content": {
"key": {
"element": "string",
"content": "x-normalized"
},
"value": {
"element": "object",
"content": [
{
"element": "member",
"content": {
"key": {
"element": "string",
"content": "operation-ids"
},
"value": {
"element": "array",
"content": [
{
"element": "string",
"content": "/paths/~1/get"
}
]
}
}
}
]
}
}
}
]
}
Expand Down Expand Up @@ -1393,6 +1515,38 @@ exports[`refractor plugins normalize-operation-ids given Operation Object with u
]
}
}
},
{
"element": "member",
"content": {
"key": {
"element": "string",
"content": "x-normalized"
},
"value": {
"element": "object",
"content": [
{
"element": "member",
"content": {
"key": {
"element": "string",
"content": "operation-ids"
},
"value": {
"element": "array",
"content": [
{
"element": "string",
"content": "/paths/~1/get"
}
]
}
}
}
]
}
}
}
]
}
Expand Down Expand Up @@ -1685,6 +1839,42 @@ exports[`refractor plugins normalize-operation-ids given Operation Objects with
]
}
}
},
{
"element": "member",
"content": {
"key": {
"element": "string",
"content": "x-normalized"
},
"value": {
"element": "object",
"content": [
{
"element": "member",
"content": {
"key": {
"element": "string",
"content": "operation-ids"
},
"value": {
"element": "array",
"content": [
{
"element": "string",
"content": "/paths/~1/get"
},
{
"element": "string",
"content": "/paths/~1/post"
}
]
}
}
}
]
}
}
}
]
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { expect } from 'chai';
import dedent from 'dedent';
import { toValue, dispatchRefractorPlugins } from '@swagger-api/apidom-core';
import { parse } from '@swagger-api/apidom-parser-adapter-yaml-1-2';

import {
createToolbox,
OpenApi3_1Element,
refractorPluginNormalizeOperationIds,
keyMap,
getNodeType,
} from '../../../../src';

describe('refractor', function () {
context('plugins', function () {
context('normalize-parameters', function () {
specify('should have idempotent characteristics', async function () {
const yamlDefinition = dedent`
openapi: 3.1.0
components:
links:
link1:
operationId: get operation ^
paths:
/:
get:
operationId: get operation ^
`;
const apiDOM = await parse(yamlDefinition);
const openApiElement = OpenApi3_1Element.refract(apiDOM.result) as OpenApi3_1Element;
const options = {
toolboxCreator: createToolbox,
visitorOptions: { keyMap, nodeTypeGetter: getNodeType },
};

dispatchRefractorPlugins(openApiElement, [refractorPluginNormalizeOperationIds()], options);
dispatchRefractorPlugins(openApiElement, [refractorPluginNormalizeOperationIds()], options);
dispatchRefractorPlugins(openApiElement, [refractorPluginNormalizeOperationIds()], options);

expect(toValue(apiDOM.result)).toMatchSnapshot();
});
});
});
});
Loading

0 comments on commit 99f83e3

Please sign in to comment.