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
  • Loading branch information
glowcloud authored May 28, 2024
1 parent 7fdf6b5 commit 858cec6
Show file tree
Hide file tree
Showing 5 changed files with 368 additions and 12 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 }) => {
const paths: string[] = [];
({
storageField = 'x-normalized',
operationIdNormalizer = normalizeOperationId,
}: PluginOptions = {}) =>
(toolbox: Toolbox) => {
const { predicates, ancestorLineageToJSONPointer, namespace } = toolbox;
const pathTemplates: 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,33 +119,55 @@ const plugin =
// cleanup the references
normalizedOperations.length = 0;
links.length = 0;
storage = undefined;
},
},
PathItemElement: {
enter(pathItemElement: PathItemElement) {
// `path` meta may not be always available, e.g. in Callback Object or Components Object
const path = defaultTo('path', toValue(pathItemElement.meta.get('path')));
paths.push(path);
const pathTemplate = defaultTo('path', toValue(pathItemElement.meta.get('path')));
pathTemplates.push(pathTemplate);
},
leave() {
paths.pop();
pathTemplates.pop();
},
},
OperationElement: {
enter(operationElement: OperationElement) {
enter(
operationElement: OperationElement,
key: string | number,
parent: Element | undefined,
path: (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
const path = last(paths) as string;
const pathTemplate = last(pathTemplates) as string;
// `http-method` meta may not be always available, e.g. in Callback Object or Components Object
const method = defaultTo(
'method',
toValue(operationElement.meta.get('http-method')),
) as string;
const normalizedOperationId = operationIdNormalizer(originalOperationId, path, method);
const normalizedOperationId = operationIdNormalizer(
originalOperationId,
pathTemplate,
method,
);

// normalization is not necessary
if (originalOperationId === normalizedOperationId) return;
Expand All @@ -140,6 +178,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
Loading

0 comments on commit 858cec6

Please sign in to comment.