1 | import { last, defaultTo, groupBy } from 'ramda';
|
---|
2 | import { toValue, cloneDeep } from '@swagger-api/apidom-core';
|
---|
3 | const removeSpaces = operationId => {
|
---|
4 | return operationId.replace(/\s/g, '');
|
---|
5 | };
|
---|
6 | const replaceSpecialCharsWithUnderscore = operationId => {
|
---|
7 | return operationId.replace(/\W/gi, '_');
|
---|
8 | };
|
---|
9 | const createNormalizedOperationId = (path, method) => {
|
---|
10 | const normalizedMethod = replaceSpecialCharsWithUnderscore(removeSpaces(method.toLowerCase()));
|
---|
11 | const normalizedPath = replaceSpecialCharsWithUnderscore(removeSpaces(path));
|
---|
12 | return `${normalizedMethod}${normalizedPath}`;
|
---|
13 | };
|
---|
14 | const normalizeOperationId = (operationId, path, method) => {
|
---|
15 | const withoutSpaces = removeSpaces(operationId);
|
---|
16 | if (withoutSpaces.length > 0) {
|
---|
17 | return replaceSpecialCharsWithUnderscore(withoutSpaces);
|
---|
18 | }
|
---|
19 | return createNormalizedOperationId(path, method);
|
---|
20 | };
|
---|
21 |
|
---|
22 | /**
|
---|
23 | * Normalization of Operation.operationId field.
|
---|
24 | *
|
---|
25 | * This normalization is not guided by OpenAPI 3.1 specification.
|
---|
26 | *
|
---|
27 | * Existing Operation.operationId fields are normalized into snake case form.
|
---|
28 | *
|
---|
29 | * Operation Objects, that do not define operationId field, are left untouched.
|
---|
30 | *
|
---|
31 | * Original operationId is stored in meta and as new `__originalOperationId` field.
|
---|
32 | *
|
---|
33 | * This plugin also guarantees the uniqueness of all defined Operation.operationId fields,
|
---|
34 | * and make sure Link.operationId fields are pointing to correct and normalized Operation.operationId fields.
|
---|
35 | *
|
---|
36 | */
|
---|
37 | /* eslint-disable no-param-reassign */
|
---|
38 |
|
---|
39 | const plugin = ({
|
---|
40 | operationIdNormalizer = normalizeOperationId
|
---|
41 | } = {}) => ({
|
---|
42 | predicates,
|
---|
43 | namespace
|
---|
44 | }) => {
|
---|
45 | const paths = [];
|
---|
46 | const normalizedOperations = [];
|
---|
47 | const links = [];
|
---|
48 | return {
|
---|
49 | visitor: {
|
---|
50 | OpenApi3_1Element: {
|
---|
51 | leave() {
|
---|
52 | // group normalized operations by normalized operationId
|
---|
53 | const normalizedOperationGroups = groupBy(operationElement => {
|
---|
54 | return toValue(operationElement.operationId);
|
---|
55 | }, normalizedOperations);
|
---|
56 |
|
---|
57 | // append incremental numerical suffixes to identical operationIds
|
---|
58 | Object.entries(normalizedOperationGroups).forEach(([normalizedOperationId, operationElements]) => {
|
---|
59 | if (!Array.isArray(operationElements)) return;
|
---|
60 | if (operationElements.length <= 1) return;
|
---|
61 | operationElements.forEach((operationElement, index) => {
|
---|
62 | const indexedNormalizedOperationId = `${normalizedOperationId}${index + 1}`;
|
---|
63 | // @ts-ignore
|
---|
64 | operationElement.operationId = new namespace.elements.String(indexedNormalizedOperationId);
|
---|
65 | });
|
---|
66 | });
|
---|
67 |
|
---|
68 | // rectify possibly broken Link.operationId fields
|
---|
69 | links.forEach(linkElement => {
|
---|
70 | if (typeof linkElement.operationId === 'undefined') return;
|
---|
71 | const linkOperationId = String(toValue(linkElement.operationId));
|
---|
72 | const operationElement = normalizedOperations.find(normalizedOperationElement => {
|
---|
73 | const originalOperationId = toValue(normalizedOperationElement.meta.get('originalOperationId'));
|
---|
74 | return originalOperationId === linkOperationId;
|
---|
75 | });
|
---|
76 |
|
---|
77 | // Link Object doesn't need to be rectified
|
---|
78 | if (typeof operationElement === 'undefined') return;
|
---|
79 | linkElement.operationId = cloneDeep.safe(operationElement.operationId);
|
---|
80 | linkElement.meta.set('originalOperationId', linkOperationId);
|
---|
81 | linkElement.set('__originalOperationId', linkOperationId);
|
---|
82 | });
|
---|
83 |
|
---|
84 | // cleanup the references
|
---|
85 | normalizedOperations.length = 0;
|
---|
86 | links.length = 0;
|
---|
87 | }
|
---|
88 | },
|
---|
89 | PathItemElement: {
|
---|
90 | enter(pathItemElement) {
|
---|
91 | // `path` meta may not be always available, e.g. in Callback Object or Components Object
|
---|
92 | const path = defaultTo('path', toValue(pathItemElement.meta.get('path')));
|
---|
93 | paths.push(path);
|
---|
94 | },
|
---|
95 | leave() {
|
---|
96 | paths.pop();
|
---|
97 | }
|
---|
98 | },
|
---|
99 | OperationElement: {
|
---|
100 | enter(operationElement) {
|
---|
101 | // operationId field is undefined, needs no normalization
|
---|
102 | if (typeof operationElement.operationId === 'undefined') return;
|
---|
103 |
|
---|
104 | // cast operationId to string type
|
---|
105 | const originalOperationId = String(toValue(operationElement.operationId));
|
---|
106 | // perform operationId normalization
|
---|
107 | const path = last(paths);
|
---|
108 | // `http-method` meta may not be always available, e.g. in Callback Object or Components Object
|
---|
109 | const method = defaultTo('method', toValue(operationElement.meta.get('http-method')));
|
---|
110 | const normalizedOperationId = operationIdNormalizer(originalOperationId, path, method);
|
---|
111 |
|
---|
112 | // normalization is not necessary
|
---|
113 | if (originalOperationId === normalizedOperationId) return;
|
---|
114 |
|
---|
115 | // @ts-ignore
|
---|
116 | operationElement.operationId = new namespace.elements.String(normalizedOperationId);
|
---|
117 | operationElement.set('__originalOperationId', originalOperationId);
|
---|
118 | operationElement.meta.set('originalOperationId', originalOperationId);
|
---|
119 | normalizedOperations.push(operationElement);
|
---|
120 | }
|
---|
121 | },
|
---|
122 | LinkElement: {
|
---|
123 | leave(linkElement) {
|
---|
124 | // make sure this Link elements doesn't come from base namespace
|
---|
125 | if (!predicates.isLinkElement(linkElement)) return;
|
---|
126 | // ignore Link Objects with undefined operationId
|
---|
127 | if (typeof linkElement.operationId === 'undefined') return;
|
---|
128 | links.push(linkElement);
|
---|
129 | }
|
---|
130 | }
|
---|
131 | }
|
---|
132 | };
|
---|
133 | };
|
---|
134 | /* eslint-enable */
|
---|
135 |
|
---|
136 | export default plugin; |
---|