Skip to content

Commit 91c700d

Browse files
authored
feat(ns-api-design-systems): add selector mechanism (#1221)
1 parent 4ba6617 commit 91c700d

File tree

10 files changed

+1313
-339
lines changed

10 files changed

+1313
-339
lines changed

packages/apidom-ns-api-design-systems/README.md

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -145,14 +145,15 @@ InfoElement.refract(objectElement, { plugins: [plugin] }); // => InfoElement({ t
145145
You can define as many plugins as needed to enhance the resulting namespaced ApiDOM structure.
146146
If multiple plugins with the same visitor method are defined, they run in parallel (just like in Babel).
147147

148-
#### OpenAPI 3.1 Standard Identifier plugin
148+
#### OpenAPI 3.1 Standard Identifier Selectors plugin
149149

150150
This plugin is specific to OpenAPI 3.1 specification and decorates significant
151-
OpenAPI 3.1 elements with [Standard Identifiers](https://apidesign.systems/standards/).
151+
OpenAPI 3.1 elements with [Standard Identifiers](https://apidesign.systems/standards/) used
152+
for [Scenario.when](https://apidesign.systems/specification/#scenario) field.
152153

153154
```js
154155
import { parse } from '@swagger-api/apidom-parser-adapter-json';
155-
import { refractPluginOpenApi3_1StandardIdentifier } from '@swagger-api/apidom-ns-api-design-systems';
156+
import { refractPluginOpenApi3_1StandardIdentifierSelectors } from '@swagger-api/apidom-ns-api-design-systems';
156157
import { OpenApi3_1Element } from '@swagger-api/apidom-ns-openapi-3-1';
157158

158159
const jsonDefinition = `
@@ -203,10 +204,10 @@ const jsonDefinition = `
203204
}
204205
`;
205206
const apiDOM = await parse(jsonDefinition);
206-
const apenApiElement = OpenApi3_1Element.refract(apiDOM.result, {
207+
const openApiElement = OpenApi3_1Element.refract(apiDOM.result, {
207208
plugins: [refractPluginOpenApi3_1StandardIdentifier()],
208209
});
209-
// => PathItemElement now contains [['http', 'transaction']] under `ads-standard-identifier` key
210+
// => OperationElement now contains [['http', 'transaction']] under `ads-s-standard-identifier` key
210211
// => other elements are decorated by different metadata as well
211212
```
212213

packages/apidom-ns-api-design-systems/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ export { default as mediaTypes, ApiDesignSystemsMediaTypes } from './media-types
1818
// eslint-disable-next-line no-restricted-exports
1919
export { default } from './namespace';
2020

21-
export { default as refractPluginOpenApi3_1StandardIdentifier } from './refractor/plugins/openapi-3-1-standard-identifier';
21+
export { default as refractPluginOpenApi3_1StandardIdentifierSelectors } from './refractor/plugins/openapi-3-1/standard-identifier-selectors';
2222

2323
export {
2424
isInfoElement,

packages/apidom-ns-api-design-systems/src/refractor/plugins/openapi-3-1-standard-identifier.ts

Lines changed: 0 additions & 77 deletions
This file was deleted.
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
import {
2+
PathItemElement,
3+
ParameterElement,
4+
RequestBodyElement,
5+
ResponsesElement,
6+
ResponseElement,
7+
OperationElement,
8+
isStringElement,
9+
isObjectElement,
10+
} from '@swagger-api/apidom-ns-openapi-3-1';
11+
12+
const plugin = () => () => {
13+
return {
14+
visitor: {
15+
OperationElement(element: OperationElement, ...rest: any) {
16+
const [, , , ancestors] = rest;
17+
const parentPathItem: PathItemElement = ancestors[ancestors.length - 2];
18+
const standardIdentifiers = [
19+
['http', 'transaction'],
20+
['http', 'request'],
21+
['http', 'request', 'url'],
22+
['http', 'request', 'url', parentPathItem.meta.get('path').toValue()],
23+
['http', 'request', 'method'],
24+
['http', 'request', 'method', element.meta.get('http-method').toValue().toLowerCase()],
25+
];
26+
27+
// fold PathItem.parameters to Operation.parameters
28+
// @ts-ignore
29+
parentPathItem?.parameters.forEach((parameter: ParameterElement) => {
30+
if (
31+
isStringElement(parameter.in) &&
32+
isStringElement(parameter.name) &&
33+
parameter.in.toValue() === 'header'
34+
) {
35+
standardIdentifiers.push(['http', 'request', 'header']);
36+
standardIdentifiers.push(['http', 'request', 'header', parameter.name.toValue()]);
37+
standardIdentifiers.push(['http', 'message', 'header']);
38+
standardIdentifiers.push(['http', 'message', 'header', parameter.name.toValue()]);
39+
}
40+
});
41+
42+
element.setMetaProperty('ads-s-standard-identifier', standardIdentifiers);
43+
},
44+
ParameterElement(element: ParameterElement) {
45+
if (
46+
isStringElement(element.in) &&
47+
isStringElement(element.name) &&
48+
element.in.toValue() === 'header'
49+
) {
50+
element.name.setMetaProperty('ads-s-standard-identifier', [
51+
['http', 'request', 'header'],
52+
['http', 'request', 'header', element.name.toValue()],
53+
['http', 'message', 'header'],
54+
['http', 'message', 'header', element.name.toValue()],
55+
]);
56+
}
57+
},
58+
RequestBodyElement(element: RequestBodyElement) {
59+
if (!isObjectElement(element.contentProp)) return;
60+
61+
element.setMetaProperty('ads-s-standard-identifier', [
62+
['http', 'request', 'body'],
63+
['http', 'message', 'body'],
64+
]);
65+
},
66+
ResponsesElement(element: ResponsesElement) {
67+
element.forEach((value, key) => {
68+
const statusCode = String(key.toValue());
69+
const statusCodeAlias = statusCode.startsWith('2')
70+
? 'success'
71+
: statusCode.startsWith('3')
72+
? 'redirect'
73+
: statusCode.startsWith('4')
74+
? 'client_error'
75+
: statusCode.startsWith('5')
76+
? 'sever_error'
77+
: 'unknown';
78+
79+
key.setMetaProperty('ads-s-standard-identifier', [
80+
['http', 'response', 'status_code'],
81+
['http', 'response', 'status_code', statusCode],
82+
['http', 'response', 'status_code', statusCodeAlias],
83+
]);
84+
});
85+
},
86+
ResponseElement(element: ResponseElement) {
87+
element.setMetaProperty('add-s-standard-identifier', [['http', 'response']]);
88+
89+
if (typeof element.headers !== 'undefined' && isObjectElement(element.headers)) {
90+
element.headers.forEach((value, key) => {
91+
const headerName = key.toValue();
92+
93+
value.setMetaProperty('ads-s-standard-identifier', [
94+
['http', 'response', 'header'],
95+
['http', 'response', 'header', headerName],
96+
['http', 'message', 'header', headerName],
97+
]);
98+
});
99+
}
100+
101+
if (typeof element.contentProp !== 'undefined' && isObjectElement(element.contentProp)) {
102+
element.contentProp.setMetaProperty('add-s-standard-identifier', [
103+
['http', 'response', 'body'],
104+
['http', 'message', 'body'],
105+
]);
106+
107+
element.contentProp.forEach((value, key) => {
108+
const headerName = key.toValue();
109+
110+
value.setMetaProperty('ads-standard-identifier', [
111+
['http', 'response', 'header'],
112+
['http', 'response', 'header', headerName],
113+
['http', 'message', 'header', headerName],
114+
]);
115+
});
116+
}
117+
},
118+
},
119+
};
120+
};
121+
122+
export default plugin;
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { includes } from 'ramda';
2+
import { Element, visit, BREAK } from '@swagger-api/apidom-core';
3+
import { OperationElement, getNodeType, keyMap } from '@swagger-api/apidom-ns-openapi-3-1';
4+
5+
import StandardIdentifierElement from '../../elements/StandardIdentifier';
6+
7+
/**
8+
* This file contains logic for translating Standard Identifier to list of Operation Elements.
9+
*/
10+
11+
const visitorOptions = { keyMap, nodeTypeGetter: getNodeType };
12+
13+
const makeStandardIdentifierVisitor = (standardIdentifier: StandardIdentifierElement) => ({
14+
hasMatch: false,
15+
enter(element: Element) {
16+
if (!element.meta.hasKey('ads-s-standard-identifier')) return undefined;
17+
18+
const standardIdentifiers = element.meta.get('ads-s-standard-identifier').toValue();
19+
20+
if (includes(standardIdentifier.toValue(), standardIdentifiers)) {
21+
this.hasMatch = true;
22+
return BREAK;
23+
}
24+
25+
return undefined;
26+
},
27+
});
28+
29+
const select = <T extends Element>(
30+
element: T,
31+
standardIdentifier: StandardIdentifierElement,
32+
): OperationElement[] => {
33+
const selected: OperationElement[] = [];
34+
const visitor = {
35+
OperationElement(operationElement: OperationElement) {
36+
const standardIdentifierVisitor = makeStandardIdentifierVisitor(standardIdentifier);
37+
38+
visit(operationElement, standardIdentifierVisitor, visitorOptions);
39+
40+
if (standardIdentifierVisitor.hasMatch) {
41+
selected.push(operationElement);
42+
}
43+
44+
return false;
45+
},
46+
};
47+
48+
visit(element, visitor, visitorOptions);
49+
50+
return selected;
51+
};
52+
53+
export default select;

0 commit comments

Comments
 (0)