@jentic/arazzo-resolver 1.0.0-alpha.32 → 1.0.0-alpha.33
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/dist/jentic-arazzo-resolver.browser.js +1116 -224
- package/dist/jentic-arazzo-resolver.browser.min.js +1 -1
- package/package.json +3 -3
- package/src/dereference/path-items.cjs +117 -0
- package/src/dereference/path-items.mjs +111 -0
- package/src/index.cjs +7 -0
- package/src/index.mjs +1 -0
- package/types/arazzo-resolver.d.ts +20 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jentic/arazzo-resolver",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.33",
|
|
4
4
|
"description": "Resolver for Arazzo Documents.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"arazzo",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"license": "Apache-2.0",
|
|
50
50
|
"dependencies": {
|
|
51
51
|
"@babel/runtime-corejs3": "^8.0.0",
|
|
52
|
-
"@jentic/arazzo-parser": "1.0.0-alpha.
|
|
52
|
+
"@jentic/arazzo-parser": "1.0.0-alpha.33",
|
|
53
53
|
"@speclynx/apidom-datamodel": "^5.0.1",
|
|
54
54
|
"@speclynx/apidom-error": "^5.0.1",
|
|
55
55
|
"@speclynx/apidom-ns-arazzo-1": "^5.0.1",
|
|
@@ -72,5 +72,5 @@
|
|
|
72
72
|
"README.md",
|
|
73
73
|
"CHANGELOG.md"
|
|
74
74
|
],
|
|
75
|
-
"gitHead": "
|
|
75
|
+
"gitHead": "a17270442ac27615af0348fad9c71817d818151f"
|
|
76
76
|
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.dereferenceSourceDescriptionPathItems = dereferenceSourceDescriptionPathItems;
|
|
7
|
+
var _apidomDatamodel = require("@speclynx/apidom-datamodel");
|
|
8
|
+
var _empty = require("@speclynx/apidom-reference/configuration/empty");
|
|
9
|
+
var _apidomNsArazzo = require("@speclynx/apidom-ns-arazzo-1");
|
|
10
|
+
var _apidomNsOpenapi = require("@speclynx/apidom-ns-openapi-2");
|
|
11
|
+
var _apidomNsOpenapi2 = require("@speclynx/apidom-ns-openapi-3-0");
|
|
12
|
+
var _apidomNsOpenapi3 = require("@speclynx/apidom-ns-openapi-3-1");
|
|
13
|
+
var _openapi = require("./openapi.cjs");
|
|
14
|
+
const isPathItem = element => (0, _apidomNsOpenapi.isPathItemElement)(element) || (0, _apidomNsOpenapi2.isPathItemElement)(element);
|
|
15
|
+
const MAX_REF_CHAIN = 20;
|
|
16
|
+
|
|
17
|
+
// evaluates the JSON Pointer of a URI fragment against an element tree
|
|
18
|
+
const evaluateFragment = (root, fragment) => {
|
|
19
|
+
const tokens = decodeURIComponent(fragment.replace(/^#/, '')).split('/').slice(1).map(token => token.replace(/~1/g, '/').replace(/~0/g, '~'));
|
|
20
|
+
let current = root;
|
|
21
|
+
for (const token of tokens) {
|
|
22
|
+
if ((0, _apidomDatamodel.isObjectElement)(current)) current = current.get(token);else if ((0, _apidomDatamodel.isArrayElement)(current)) current = current.get(Number(token));else return undefined;
|
|
23
|
+
}
|
|
24
|
+
return current;
|
|
25
|
+
};
|
|
26
|
+
const refractorFor = api => {
|
|
27
|
+
if ((0, _apidomNsOpenapi.isSwaggerElement)(api)) return _apidomNsOpenapi.refractPathItem;
|
|
28
|
+
if ((0, _apidomNsOpenapi2.isOpenApi3_0Element)(api)) return _apidomNsOpenapi2.refractPathItem;
|
|
29
|
+
if ((0, _apidomNsOpenapi3.isOpenApi3_1Element)(api)) return _apidomNsOpenapi3.refractPathItem;
|
|
30
|
+
return undefined;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Replaces the Path Items that consist of a `$ref` in the `openapi` source descriptions parsed
|
|
35
|
+
* along with an Arazzo Document (see the `sourceDescriptions` parser option) with the Path Items
|
|
36
|
+
* they point to, whether local (`#/components/pathItems/...`) or external.
|
|
37
|
+
*
|
|
38
|
+
* Only the chain of Path Item `$ref`s is followed. Nothing inside the referenced Path Items
|
|
39
|
+
* (responses, schemas, ...) is dereferenced, so an unrelated nested reference that can't be
|
|
40
|
+
* resolved never hides the operations. The source descriptions are modified in place, which makes
|
|
41
|
+
* the operations behind such Path Items discoverable, e.g. by `findStepOperation` of
|
|
42
|
+
* \@jentic/arazzo-parser.
|
|
43
|
+
*
|
|
44
|
+
* This is best effort and never rejects: a Path Item that can't be resolved is left as is.
|
|
45
|
+
*
|
|
46
|
+
* @param parseResult - The result of parsing an Arazzo Document with source descriptions
|
|
47
|
+
* @param options - Reference options (uses defaultOptions when not provided)
|
|
48
|
+
* @returns A promise that resolves once all resolvable Path Items have been replaced
|
|
49
|
+
* @public
|
|
50
|
+
*/
|
|
51
|
+
async function dereferenceSourceDescriptionPathItems(parseResult, options = {}) {
|
|
52
|
+
if (!(0, _apidomNsArazzo.isArazzoSpecification1Element)(parseResult.api)) return;
|
|
53
|
+
const mergedOptions = (0, _empty.mergeOptions)(_openapi.defaultOptions, options);
|
|
54
|
+
// external documents are fetched once, however many Path Items point into them
|
|
55
|
+
const documents = new Map();
|
|
56
|
+
const fetchDocument = uri => {
|
|
57
|
+
if (!documents.has(uri)) documents.set(uri, (0, _empty.parse)(uri, mergedOptions));
|
|
58
|
+
return documents.get(uri);
|
|
59
|
+
};
|
|
60
|
+
const pending = [];
|
|
61
|
+
parseResult.api.sourceDescriptions?.forEach(sourceDescription => {
|
|
62
|
+
if (!(0, _apidomNsArazzo.isSourceDescriptionElement)(sourceDescription)) return;
|
|
63
|
+
const sourceParseResult = sourceDescription.meta.get('parseResult');
|
|
64
|
+
const api = sourceParseResult?.api;
|
|
65
|
+
const sourceURI = sourceParseResult?.meta.get('retrievalURI');
|
|
66
|
+
if (!(0, _apidomNsOpenapi.isSwaggerElement)(api) && !(0, _apidomNsOpenapi2.isOpenApi3_0Element)(api) && !(0, _apidomNsOpenapi3.isOpenApi3_1Element)(api)) return;
|
|
67
|
+
const refract = refractorFor(api);
|
|
68
|
+
if (!refract || !sourceURI) return;
|
|
69
|
+
|
|
70
|
+
// follows the `$ref` of a Path Item to the Path Item it points to
|
|
71
|
+
const follow = async (pathItem, baseURI, root, visited) => {
|
|
72
|
+
if (!(0, _apidomDatamodel.isStringElement)(pathItem.$ref)) return pathItem;
|
|
73
|
+
const ref = pathItem.$ref.toValue();
|
|
74
|
+
const documentURI = _empty.url.resolve(baseURI, _empty.url.stripHash(ref));
|
|
75
|
+
const fragment = _empty.url.getHash(ref);
|
|
76
|
+
const key = `${documentURI}${fragment}`;
|
|
77
|
+
if (visited.has(key) || visited.size >= MAX_REF_CHAIN) return undefined;
|
|
78
|
+
visited.add(key);
|
|
79
|
+
let targetRoot = root;
|
|
80
|
+
if (documentURI !== _empty.url.stripHash(baseURI)) {
|
|
81
|
+
const document = await fetchDocument(documentURI);
|
|
82
|
+
targetRoot = (0, _apidomDatamodel.isObjectElement)(document.api) ? document.api : document.result;
|
|
83
|
+
}
|
|
84
|
+
const evaluated = evaluateFragment(targetRoot, fragment);
|
|
85
|
+
const target = isPathItem(evaluated) ? evaluated : refract(evaluated);
|
|
86
|
+
if (!isPathItem(target)) return undefined;
|
|
87
|
+
|
|
88
|
+
// fields next to the `$ref` override the ones of the referenced Path Item
|
|
89
|
+
const merged = (0, _apidomDatamodel.cloneShallow)(target);
|
|
90
|
+
if ((0, _apidomDatamodel.isObjectElement)(pathItem)) {
|
|
91
|
+
pathItem.forEach((value, member) => {
|
|
92
|
+
const name = String(member.toValue());
|
|
93
|
+
if (name !== '$ref') merged.set(name, value);
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
return follow(merged, documentURI, targetRoot, visited);
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
// Path Items in `paths` and in the reusable `components.pathItems` are both resolved, since
|
|
100
|
+
// steps can target either
|
|
101
|
+
const collections = [api.paths, api.components?.pathItems];
|
|
102
|
+
collections.forEach(collection => {
|
|
103
|
+
if (!(0, _apidomDatamodel.isObjectElement)(collection)) return;
|
|
104
|
+
collection.forEach((pathItem, member) => {
|
|
105
|
+
// in `components.pathItems` a `$ref`-only entry is refracted to a Reference, not a Path Item
|
|
106
|
+
const candidate = pathItem;
|
|
107
|
+
if (!(0, _apidomDatamodel.isObjectElement)(pathItem) || !(0, _apidomDatamodel.isStringElement)(candidate.$ref)) return;
|
|
108
|
+
pending.push(follow(candidate, sourceURI, api, new Set()).then(resolved => {
|
|
109
|
+
if (resolved) collection.set(String(member.toValue()), resolved);
|
|
110
|
+
}, () => {
|
|
111
|
+
// keep the unresolved Path Item
|
|
112
|
+
}));
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
await Promise.all(pending);
|
|
117
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { cloneShallow, isArrayElement, isObjectElement, isStringElement } from '@speclynx/apidom-datamodel';
|
|
2
|
+
import { mergeOptions, parse, url } from '@speclynx/apidom-reference/configuration/empty';
|
|
3
|
+
import { isArazzoSpecification1Element, isSourceDescriptionElement } from '@speclynx/apidom-ns-arazzo-1';
|
|
4
|
+
import { isSwaggerElement, isPathItemElement as isPathItem2Element, refractPathItem as refractPathItem2 } from '@speclynx/apidom-ns-openapi-2';
|
|
5
|
+
import { isOpenApi3_0Element, isPathItemElement as isPathItem3Element, refractPathItem as refractPathItem3_0 } from '@speclynx/apidom-ns-openapi-3-0';
|
|
6
|
+
import { isOpenApi3_1Element, refractPathItem as refractPathItem3_1 } from '@speclynx/apidom-ns-openapi-3-1';
|
|
7
|
+
import { defaultOptions as defaultOpenAPIOptions } from "./openapi.mjs";
|
|
8
|
+
const isPathItem = element => isPathItem2Element(element) || isPathItem3Element(element);
|
|
9
|
+
const MAX_REF_CHAIN = 20;
|
|
10
|
+
|
|
11
|
+
// evaluates the JSON Pointer of a URI fragment against an element tree
|
|
12
|
+
const evaluateFragment = (root, fragment) => {
|
|
13
|
+
const tokens = decodeURIComponent(fragment.replace(/^#/, '')).split('/').slice(1).map(token => token.replace(/~1/g, '/').replace(/~0/g, '~'));
|
|
14
|
+
let current = root;
|
|
15
|
+
for (const token of tokens) {
|
|
16
|
+
if (isObjectElement(current)) current = current.get(token);else if (isArrayElement(current)) current = current.get(Number(token));else return undefined;
|
|
17
|
+
}
|
|
18
|
+
return current;
|
|
19
|
+
};
|
|
20
|
+
const refractorFor = api => {
|
|
21
|
+
if (isSwaggerElement(api)) return refractPathItem2;
|
|
22
|
+
if (isOpenApi3_0Element(api)) return refractPathItem3_0;
|
|
23
|
+
if (isOpenApi3_1Element(api)) return refractPathItem3_1;
|
|
24
|
+
return undefined;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Replaces the Path Items that consist of a `$ref` in the `openapi` source descriptions parsed
|
|
29
|
+
* along with an Arazzo Document (see the `sourceDescriptions` parser option) with the Path Items
|
|
30
|
+
* they point to, whether local (`#/components/pathItems/...`) or external.
|
|
31
|
+
*
|
|
32
|
+
* Only the chain of Path Item `$ref`s is followed. Nothing inside the referenced Path Items
|
|
33
|
+
* (responses, schemas, ...) is dereferenced, so an unrelated nested reference that can't be
|
|
34
|
+
* resolved never hides the operations. The source descriptions are modified in place, which makes
|
|
35
|
+
* the operations behind such Path Items discoverable, e.g. by `findStepOperation` of
|
|
36
|
+
* \@jentic/arazzo-parser.
|
|
37
|
+
*
|
|
38
|
+
* This is best effort and never rejects: a Path Item that can't be resolved is left as is.
|
|
39
|
+
*
|
|
40
|
+
* @param parseResult - The result of parsing an Arazzo Document with source descriptions
|
|
41
|
+
* @param options - Reference options (uses defaultOptions when not provided)
|
|
42
|
+
* @returns A promise that resolves once all resolvable Path Items have been replaced
|
|
43
|
+
* @public
|
|
44
|
+
*/
|
|
45
|
+
export async function dereferenceSourceDescriptionPathItems(parseResult, options = {}) {
|
|
46
|
+
if (!isArazzoSpecification1Element(parseResult.api)) return;
|
|
47
|
+
const mergedOptions = mergeOptions(defaultOpenAPIOptions, options);
|
|
48
|
+
// external documents are fetched once, however many Path Items point into them
|
|
49
|
+
const documents = new Map();
|
|
50
|
+
const fetchDocument = uri => {
|
|
51
|
+
if (!documents.has(uri)) documents.set(uri, parse(uri, mergedOptions));
|
|
52
|
+
return documents.get(uri);
|
|
53
|
+
};
|
|
54
|
+
const pending = [];
|
|
55
|
+
parseResult.api.sourceDescriptions?.forEach(sourceDescription => {
|
|
56
|
+
if (!isSourceDescriptionElement(sourceDescription)) return;
|
|
57
|
+
const sourceParseResult = sourceDescription.meta.get('parseResult');
|
|
58
|
+
const api = sourceParseResult?.api;
|
|
59
|
+
const sourceURI = sourceParseResult?.meta.get('retrievalURI');
|
|
60
|
+
if (!isSwaggerElement(api) && !isOpenApi3_0Element(api) && !isOpenApi3_1Element(api)) return;
|
|
61
|
+
const refract = refractorFor(api);
|
|
62
|
+
if (!refract || !sourceURI) return;
|
|
63
|
+
|
|
64
|
+
// follows the `$ref` of a Path Item to the Path Item it points to
|
|
65
|
+
const follow = async (pathItem, baseURI, root, visited) => {
|
|
66
|
+
if (!isStringElement(pathItem.$ref)) return pathItem;
|
|
67
|
+
const ref = pathItem.$ref.toValue();
|
|
68
|
+
const documentURI = url.resolve(baseURI, url.stripHash(ref));
|
|
69
|
+
const fragment = url.getHash(ref);
|
|
70
|
+
const key = `${documentURI}${fragment}`;
|
|
71
|
+
if (visited.has(key) || visited.size >= MAX_REF_CHAIN) return undefined;
|
|
72
|
+
visited.add(key);
|
|
73
|
+
let targetRoot = root;
|
|
74
|
+
if (documentURI !== url.stripHash(baseURI)) {
|
|
75
|
+
const document = await fetchDocument(documentURI);
|
|
76
|
+
targetRoot = isObjectElement(document.api) ? document.api : document.result;
|
|
77
|
+
}
|
|
78
|
+
const evaluated = evaluateFragment(targetRoot, fragment);
|
|
79
|
+
const target = isPathItem(evaluated) ? evaluated : refract(evaluated);
|
|
80
|
+
if (!isPathItem(target)) return undefined;
|
|
81
|
+
|
|
82
|
+
// fields next to the `$ref` override the ones of the referenced Path Item
|
|
83
|
+
const merged = cloneShallow(target);
|
|
84
|
+
if (isObjectElement(pathItem)) {
|
|
85
|
+
pathItem.forEach((value, member) => {
|
|
86
|
+
const name = String(member.toValue());
|
|
87
|
+
if (name !== '$ref') merged.set(name, value);
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
return follow(merged, documentURI, targetRoot, visited);
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
// Path Items in `paths` and in the reusable `components.pathItems` are both resolved, since
|
|
94
|
+
// steps can target either
|
|
95
|
+
const collections = [api.paths, api.components?.pathItems];
|
|
96
|
+
collections.forEach(collection => {
|
|
97
|
+
if (!isObjectElement(collection)) return;
|
|
98
|
+
collection.forEach((pathItem, member) => {
|
|
99
|
+
// in `components.pathItems` a `$ref`-only entry is refracted to a Reference, not a Path Item
|
|
100
|
+
const candidate = pathItem;
|
|
101
|
+
if (!isObjectElement(pathItem) || !isStringElement(candidate.$ref)) return;
|
|
102
|
+
pending.push(follow(candidate, sourceURI, api, new Set()).then(resolved => {
|
|
103
|
+
if (resolved) collection.set(String(member.toValue()), resolved);
|
|
104
|
+
}, () => {
|
|
105
|
+
// keep the unresolved Path Item
|
|
106
|
+
}));
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
await Promise.all(pending);
|
|
111
|
+
}
|
package/src/index.cjs
CHANGED
|
@@ -46,6 +46,13 @@ Object.defineProperty(exports, "dereferenceOpenAPIElement", {
|
|
|
46
46
|
return _openapi.dereferenceElement;
|
|
47
47
|
}
|
|
48
48
|
});
|
|
49
|
+
Object.defineProperty(exports, "dereferenceSourceDescriptionPathItems", {
|
|
50
|
+
enumerable: true,
|
|
51
|
+
get: function () {
|
|
52
|
+
return _pathItems.dereferenceSourceDescriptionPathItems;
|
|
53
|
+
}
|
|
54
|
+
});
|
|
49
55
|
var _arazzo = require("./dereference/arazzo.cjs");
|
|
50
56
|
var _openapi = require("./dereference/openapi.cjs");
|
|
57
|
+
var _pathItems = require("./dereference/path-items.cjs");
|
|
51
58
|
var _DereferenceError = _interopRequireDefault(require("./errors/DereferenceError.cjs"));
|
package/src/index.mjs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
export { defaultOptions as defaultDereferenceArazzoOptions, dereference as dereferenceArazzo, dereferenceElement as dereferenceArazzoElement } from "./dereference/arazzo.mjs";
|
|
2
2
|
export { defaultOptions as defaultDereferenceOpenAPIOptions, dereference as dereferenceOpenAPI, dereferenceElement as dereferenceOpenAPIElement } from "./dereference/openapi.mjs";
|
|
3
|
+
export { dereferenceSourceDescriptionPathItems } from "./dereference/path-items.mjs";
|
|
3
4
|
export { default as DereferenceError } from "./errors/DereferenceError.mjs";
|
|
@@ -216,4 +216,24 @@ export declare function dereferenceOpenAPIElement<T extends Element_2>(element:
|
|
|
216
216
|
*/
|
|
217
217
|
export declare type DereferenceOpenAPIOptions = PartialDeep<ApiDOMReferenceOptions>;
|
|
218
218
|
|
|
219
|
+
/**
|
|
220
|
+
* Replaces the Path Items that consist of a `$ref` in the `openapi` source descriptions parsed
|
|
221
|
+
* along with an Arazzo Document (see the `sourceDescriptions` parser option) with the Path Items
|
|
222
|
+
* they point to, whether local (`#/components/pathItems/...`) or external.
|
|
223
|
+
*
|
|
224
|
+
* Only the chain of Path Item `$ref`s is followed. Nothing inside the referenced Path Items
|
|
225
|
+
* (responses, schemas, ...) is dereferenced, so an unrelated nested reference that can't be
|
|
226
|
+
* resolved never hides the operations. The source descriptions are modified in place, which makes
|
|
227
|
+
* the operations behind such Path Items discoverable, e.g. by `findStepOperation` of
|
|
228
|
+
* \@jentic/arazzo-parser.
|
|
229
|
+
*
|
|
230
|
+
* This is best effort and never rejects: a Path Item that can't be resolved is left as is.
|
|
231
|
+
*
|
|
232
|
+
* @param parseResult - The result of parsing an Arazzo Document with source descriptions
|
|
233
|
+
* @param options - Reference options (uses defaultOptions when not provided)
|
|
234
|
+
* @returns A promise that resolves once all resolvable Path Items have been replaced
|
|
235
|
+
* @public
|
|
236
|
+
*/
|
|
237
|
+
export declare function dereferenceSourceDescriptionPathItems(parseResult: ParseResultElement, options?: DereferenceOpenAPIOptions): Promise<void>;
|
|
238
|
+
|
|
219
239
|
export { }
|