@speclynx/apidom-reference 5.1.1 → 5.2.2
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 +16 -0
- package/README.md +44 -2
- package/dist/apidom-reference.browser.js +2095 -777
- package/dist/apidom-reference.browser.min.js +1 -1
- package/package.json +31 -31
- package/src/bundle/strategies/arazzo-1/visitor.cjs +24 -8
- package/src/bundle/strategies/arazzo-1/visitor.mjs +25 -9
- package/src/dereference/strategies/arazzo-1/index.cjs +12 -0
- package/src/dereference/strategies/arazzo-1/index.mjs +1 -1
- package/src/dereference/strategies/arazzo-1/source-descriptions.cjs +43 -41
- package/src/dereference/strategies/arazzo-1/source-descriptions.mjs +43 -41
- package/src/dereference/strategies/arazzo-1/util.cjs +86 -1
- package/src/dereference/strategies/arazzo-1/util.mjs +81 -2
- package/src/dereference/strategies/arazzo-1/visitor.cjs +21 -7
- package/src/dereference/strategies/arazzo-1/visitor.mjs +22 -8
- package/src/parse/parsers/arazzo-json-1/source-descriptions.cjs +39 -38
- package/src/parse/parsers/arazzo-json-1/source-descriptions.mjs +40 -39
- package/types/bundle/strategies/arazzo-1/visitor.d.ts +6 -0
- package/types/dereference/strategies/arazzo-1/index.d.ts +1 -1
- package/types/dereference/strategies/arazzo-1/source-descriptions.d.ts +7 -0
- package/types/dereference/strategies/arazzo-1/util.d.ts +34 -1
- package/types/dereference/strategies/arazzo-1/visitor.d.ts +6 -0
- package/types/parse/parsers/arazzo-json-1/source-descriptions.d.ts +7 -0
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
import { ParseResultElement, AnnotationElement, isArrayElement, isStringElement } from '@speclynx/apidom-datamodel';
|
|
2
2
|
import { isArazzoSpecification1Element, isSourceDescriptionElement } from '@speclynx/apidom-ns-arazzo-1';
|
|
3
|
-
import { isSwaggerElement } from '@speclynx/apidom-ns-openapi-2';
|
|
4
|
-
import { isOpenApi3_0Element } from '@speclynx/apidom-ns-openapi-3-0';
|
|
5
|
-
import { isOpenApi3_1Element } from '@speclynx/apidom-ns-openapi-3-1';
|
|
6
3
|
import { toValue } from '@speclynx/apidom-core';
|
|
7
4
|
import File from "../../../File.mjs";
|
|
8
5
|
import * as url from "../../../util/url.mjs";
|
|
9
6
|
import { merge as mergeOptions } from "../../../options/util.mjs";
|
|
10
|
-
import parse from "../../index.mjs";
|
|
7
|
+
import parse from "../../index.mjs";
|
|
8
|
+
import { arazzoDocumentURIs, resolveArazzo$selfField, validateSourceDescriptionAPI } from "../../../dereference/strategies/arazzo-1/util.mjs"; // shared key for recursion state (works across JSON/YAML parsers)
|
|
11
9
|
const ARAZZO_RECURSION_KEY = 'arazzo-1';
|
|
12
10
|
/**
|
|
13
11
|
* Parses a single source description element.
|
|
@@ -38,14 +36,25 @@ async function parseSourceDescription(sourceDescription, ctx) {
|
|
|
38
36
|
const retrievalURI = url.sanitize(url.stripHash(url.resolve(ctx.baseURI, sourceDescriptionURI)));
|
|
39
37
|
parseResult.setMetaProperty('retrievalURI', retrievalURI);
|
|
40
38
|
|
|
41
|
-
// skip if
|
|
42
|
-
if (ctx.
|
|
39
|
+
// skip if the document is still being parsed higher up the chain (cycle detection)
|
|
40
|
+
if (ctx.ancestors.includes(retrievalURI)) {
|
|
43
41
|
const annotation = new AnnotationElement(`Source description "${retrievalURI}" has already been visited. Skipping to prevent cycle`);
|
|
44
42
|
annotation.classes.push('warning');
|
|
45
43
|
parseResult.push(annotation);
|
|
46
44
|
return parseResult;
|
|
47
45
|
}
|
|
48
|
-
|
|
46
|
+
|
|
47
|
+
// a document reached again through a different path is a shared dependency, not a cycle;
|
|
48
|
+
// point at the result where it was parsed instead of parsing it again
|
|
49
|
+
const sharedParseResult = ctx.parsed.get(retrievalURI);
|
|
50
|
+
if (sharedParseResult !== undefined) {
|
|
51
|
+
const annotation = new AnnotationElement(`Source description "${retrievalURI}" has already been parsed. Reusing existing parse result`);
|
|
52
|
+
annotation.classes.push('info');
|
|
53
|
+
parseResult.push(annotation);
|
|
54
|
+
parseResult.meta.set('parseResult', sharedParseResult);
|
|
55
|
+
validateSourceDescriptionAPI(parseResult, sourceDescription, sharedParseResult.api, retrievalURI);
|
|
56
|
+
return parseResult;
|
|
57
|
+
}
|
|
49
58
|
try {
|
|
50
59
|
const sourceDescriptionParseResult = await parse(retrievalURI, mergeOptions(ctx.options, {
|
|
51
60
|
parse: {
|
|
@@ -57,7 +66,8 @@ async function parseSourceDescription(sourceDescription, ctx) {
|
|
|
57
66
|
sourceDescriptions: true,
|
|
58
67
|
[ARAZZO_RECURSION_KEY]: {
|
|
59
68
|
sourceDescriptionsDepth: ctx.currentDepth + 1,
|
|
60
|
-
|
|
69
|
+
sourceDescriptionsAncestors: ctx.ancestors,
|
|
70
|
+
sourceDescriptionsParsed: ctx.parsed
|
|
61
71
|
}
|
|
62
72
|
}
|
|
63
73
|
}
|
|
@@ -75,32 +85,14 @@ async function parseSourceDescription(sourceDescription, ctx) {
|
|
|
75
85
|
return parseResult;
|
|
76
86
|
}
|
|
77
87
|
|
|
78
|
-
//
|
|
88
|
+
// register parsed document for later references to it
|
|
79
89
|
const {
|
|
80
90
|
api: sourceDescriptionAPI
|
|
81
91
|
} = parseResult;
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
if (!isOpenApi && !isArazzo) {
|
|
85
|
-
const annotation = new AnnotationElement(`Source description "${retrievalURI}" is not an OpenAPI or Arazzo document`);
|
|
86
|
-
annotation.classes.push('warning');
|
|
87
|
-
parseResult.push(annotation);
|
|
88
|
-
return parseResult;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// validate declared type matches actual parsed type
|
|
92
|
-
const declaredType = toValue(sourceDescription.type);
|
|
93
|
-
if (typeof declaredType === 'string') {
|
|
94
|
-
if (declaredType === 'openapi' && !isOpenApi) {
|
|
95
|
-
const annotation = new AnnotationElement(`Source description "${retrievalURI}" declared as "openapi" but parsed as Arazzo document`);
|
|
96
|
-
annotation.classes.push('warning');
|
|
97
|
-
parseResult.push(annotation);
|
|
98
|
-
} else if (declaredType === 'arazzo' && !isArazzo) {
|
|
99
|
-
const annotation = new AnnotationElement(`Source description "${retrievalURI}" declared as "arazzo" but parsed as OpenAPI document`);
|
|
100
|
-
annotation.classes.push('warning');
|
|
101
|
-
parseResult.push(annotation);
|
|
102
|
-
}
|
|
92
|
+
for (const documentURI of arazzoDocumentURIs(retrievalURI, sourceDescriptionAPI)) {
|
|
93
|
+
ctx.parsed.set(documentURI, parseResult);
|
|
103
94
|
}
|
|
95
|
+
validateSourceDescriptionAPI(parseResult, sourceDescription, sourceDescriptionAPI, retrievalURI);
|
|
104
96
|
return parseResult;
|
|
105
97
|
}
|
|
106
98
|
|
|
@@ -112,6 +104,13 @@ async function parseSourceDescription(sourceDescription, ctx) {
|
|
|
112
104
|
* regardless of success or failure. On failure, the ParseResultElement
|
|
113
105
|
* contains annotations explaining what went wrong.
|
|
114
106
|
*
|
|
107
|
+
* A document referenced from more than one place is parsed only once.
|
|
108
|
+
* Every later source description referencing it gets its own result carrying
|
|
109
|
+
* an 'info' annotation and a 'parseResult' meta pointing at the result where
|
|
110
|
+
* the document was parsed; that result is also what gets attached to the
|
|
111
|
+
* SourceDescriptionElement's meta. A document referencing one of its ancestors
|
|
112
|
+
* forms a cycle and is skipped with a 'warning' annotation instead.
|
|
113
|
+
*
|
|
115
114
|
* @param parseResult - ParseResult containing an Arazzo specification
|
|
116
115
|
* @param parseResultRetrievalURI - URI from which the parseResult was retrieved
|
|
117
116
|
* @param options - Full ReferenceOptions. Pass `sourceDescriptions` as an array of names
|
|
@@ -177,10 +176,9 @@ export async function parseSourceDescriptions(parseResult, parseResultRetrievalU
|
|
|
177
176
|
// recursion state comes from shared key (works across JSON/YAML)
|
|
178
177
|
const sharedOpts = options?.parse?.parserOpts?.[ARAZZO_RECURSION_KEY] ?? {};
|
|
179
178
|
const currentDepth = sharedOpts.sourceDescriptionsDepth ?? 0;
|
|
180
|
-
const
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
visitedUrls.add(file.uri);
|
|
179
|
+
const parsed = sharedOpts.sourceDescriptionsParsed ?? new Map();
|
|
180
|
+
// current file is an ancestor of everything parsed below
|
|
181
|
+
const ancestors = [...(sharedOpts.sourceDescriptionsAncestors ?? []), ...arazzoDocumentURIs(file.uri, api)];
|
|
184
182
|
if (currentDepth >= maxDepth) {
|
|
185
183
|
const annotation = new AnnotationElement(`Maximum parse depth of ${maxDepth} has been exceeded by file "${file.uri}"`);
|
|
186
184
|
annotation.classes.push('error');
|
|
@@ -189,10 +187,12 @@ export async function parseSourceDescriptions(parseResult, parseResultRetrievalU
|
|
|
189
187
|
return [parseResult];
|
|
190
188
|
}
|
|
191
189
|
const ctx = {
|
|
192
|
-
|
|
190
|
+
// base URI for resolving source description URLs honors Arazzo `$self` field
|
|
191
|
+
baseURI: resolveArazzo$selfField(file.uri, api),
|
|
193
192
|
options,
|
|
194
193
|
currentDepth,
|
|
195
|
-
|
|
194
|
+
ancestors,
|
|
195
|
+
parsed
|
|
196
196
|
};
|
|
197
197
|
|
|
198
198
|
// determine which source descriptions to parse (array filters by name)
|
|
@@ -203,11 +203,12 @@ export async function parseSourceDescriptions(parseResult, parseResultRetrievalU
|
|
|
203
203
|
return typeof name === 'string' && sourceDescriptionsOption.includes(name);
|
|
204
204
|
}) : api.sourceDescriptions;
|
|
205
205
|
|
|
206
|
-
// process sequentially to ensure proper cycle detection with shared
|
|
206
|
+
// process sequentially to ensure proper cycle detection with shared state
|
|
207
207
|
for (const sourceDescription of sourceDescriptions) {
|
|
208
208
|
const sourceDescriptionParseResult = await parseSourceDescription(sourceDescription, ctx);
|
|
209
|
-
// always attach result (even on failure - contains annotations)
|
|
210
|
-
|
|
209
|
+
// always attach result (even on failure - contains annotations);
|
|
210
|
+
// a shared document attaches the result where it was parsed
|
|
211
|
+
sourceDescription.meta.set('parseResult', sourceDescriptionParseResult.meta.get('parseResult') ?? sourceDescriptionParseResult);
|
|
211
212
|
results.push(sourceDescriptionParseResult);
|
|
212
213
|
}
|
|
213
214
|
return results;
|
|
@@ -17,6 +17,12 @@ export interface Arazzo1BundleVisitorOptions {
|
|
|
17
17
|
*/
|
|
18
18
|
declare class Arazzo1BundleVisitor {
|
|
19
19
|
protected readonly reference: Reference;
|
|
20
|
+
/**
|
|
21
|
+
* Base URI of the current document for resolving relative references.
|
|
22
|
+
* Honors the Arazzo `$self` field when present, otherwise falls back
|
|
23
|
+
* to the retrieval URI (`this.reference.uri`).
|
|
24
|
+
*/
|
|
25
|
+
protected readonly baseURI: string;
|
|
20
26
|
protected readonly options: ReferenceOptions;
|
|
21
27
|
/**
|
|
22
28
|
* The entry parse result. Derived from the shared refSet's root reference.
|
|
@@ -28,6 +28,6 @@ declare class Arazzo1DereferenceStrategy extends DereferenceStrategy {
|
|
|
28
28
|
dereference(file: File, options: ReferenceOptions): Promise<Element>;
|
|
29
29
|
}
|
|
30
30
|
export { Arazzo1DereferenceVisitor };
|
|
31
|
-
export { resolveSchema$refField, resolveSchema$idField, maybeRefractToJSONSchemaElement, } from './util.ts';
|
|
31
|
+
export { resolveSchema$refField, resolveSchema$idField, resolveArazzo$selfField, identifiedBy$self, maybeRefractToJSONSchemaElement, } from './util.ts';
|
|
32
32
|
export { dereferenceSourceDescriptions } from './source-descriptions.ts';
|
|
33
33
|
export default Arazzo1DereferenceStrategy;
|
|
@@ -8,6 +8,13 @@ import type { ReferenceOptions } from '../../../options/index.ts';
|
|
|
8
8
|
* regardless of success or failure. On failure, the ParseResultElement
|
|
9
9
|
* contains annotations explaining what went wrong.
|
|
10
10
|
*
|
|
11
|
+
* A document referenced from more than one place is dereferenced only once.
|
|
12
|
+
* Every later source description referencing it gets its own result carrying
|
|
13
|
+
* an 'info' annotation and a 'parseResult' meta pointing at the result where
|
|
14
|
+
* the document was dereferenced; that result is also what gets attached to the
|
|
15
|
+
* SourceDescriptionElement's meta. A document referencing one of its ancestors
|
|
16
|
+
* forms a cycle and is skipped with a 'warning' annotation instead.
|
|
17
|
+
*
|
|
11
18
|
* @param parseResult - ParseResult containing a parsed (optionally dereferenced) Arazzo specification
|
|
12
19
|
* @param parseResultRetrievalURI - URI from which the parseResult was retrieved
|
|
13
20
|
* @param options - Full ReferenceOptions. Pass `sourceDescriptions` as an array of names
|
|
@@ -1,5 +1,38 @@
|
|
|
1
|
-
import { Element } from '@speclynx/apidom-datamodel';
|
|
1
|
+
import { Element, ParseResultElement } from '@speclynx/apidom-datamodel';
|
|
2
|
+
import { SourceDescriptionElement } from '@speclynx/apidom-ns-arazzo-1';
|
|
3
|
+
import type Reference from '../../../Reference.ts';
|
|
2
4
|
export { resolveSchema$refField, resolveSchema$idField } from '../openapi-3-1/util.ts';
|
|
5
|
+
/**
|
|
6
|
+
* Computes the base URI of an Arazzo document per Arazzo 1.1.0 rules.
|
|
7
|
+
*
|
|
8
|
+
* When the Arazzo Object carries a `$self` field, it establishes the document's
|
|
9
|
+
* canonical identity and the base URI for resolving relative references. `$self`
|
|
10
|
+
* may itself be relative, in which case it's resolved against the retrieval URI.
|
|
11
|
+
* Any fragment is stripped, as `$self` MUST NOT contain one. When `$self` is
|
|
12
|
+
* absent (or `element` is not an Arazzo document), the retrieval URI is the base URI.
|
|
13
|
+
*
|
|
14
|
+
* @public
|
|
15
|
+
*/
|
|
16
|
+
export declare const resolveArazzo$selfField: (retrievalURI: string, element: unknown) => string;
|
|
17
|
+
/**
|
|
18
|
+
* URIs an Arazzo document is known by: its retrieval URI and, when the
|
|
19
|
+
* document carries a `$self` field resolving elsewhere, its identity.
|
|
20
|
+
*/
|
|
21
|
+
export declare const arazzoDocumentURIs: (retrievalURI: string, element: unknown) => string[];
|
|
22
|
+
/**
|
|
23
|
+
* Annotates a source description result when the document is not an OpenAPI
|
|
24
|
+
* or Arazzo document, or does not match the type the source description declares.
|
|
25
|
+
*/
|
|
26
|
+
export declare const validateSourceDescriptionAPI: (parseResult: ParseResultElement, sourceDescription: SourceDescriptionElement, sourceDescriptionAPI: Element | undefined, retrievalURI: string) => void;
|
|
27
|
+
/**
|
|
28
|
+
* Predicate matching a Reference by the identity (`$self`) of the Arazzo
|
|
29
|
+
* document it holds, rather than by its retrieval URI. Implements identity-based
|
|
30
|
+
* referencing from Arazzo 1.1.0: a URI matching a loaded document's `$self`
|
|
31
|
+
* refers to that document, regardless of where it was retrieved from.
|
|
32
|
+
*
|
|
33
|
+
* @public
|
|
34
|
+
*/
|
|
35
|
+
export declare const identifiedBy$self: (uri: string) => (reference: Reference) => boolean;
|
|
3
36
|
/**
|
|
4
37
|
* Cached version of JSONSchemaElement.refract.
|
|
5
38
|
*/
|
|
@@ -19,6 +19,12 @@ export interface Arazzo1DereferenceVisitorOptions {
|
|
|
19
19
|
declare class Arazzo1DereferenceVisitor {
|
|
20
20
|
protected readonly indirections: Element[];
|
|
21
21
|
protected readonly reference: Reference;
|
|
22
|
+
/**
|
|
23
|
+
* Base URI of the current document for resolving relative references.
|
|
24
|
+
* Honors the Arazzo `$self` field when present, otherwise falls back
|
|
25
|
+
* to the retrieval URI (`this.reference.uri`).
|
|
26
|
+
*/
|
|
27
|
+
protected readonly baseURI: string;
|
|
22
28
|
protected readonly options: ReferenceOptions;
|
|
23
29
|
protected readonly visited: WeakSet<Element>;
|
|
24
30
|
/**
|
|
@@ -8,6 +8,13 @@ import type { ReferenceOptions } from '../../../options/index.ts';
|
|
|
8
8
|
* regardless of success or failure. On failure, the ParseResultElement
|
|
9
9
|
* contains annotations explaining what went wrong.
|
|
10
10
|
*
|
|
11
|
+
* A document referenced from more than one place is parsed only once.
|
|
12
|
+
* Every later source description referencing it gets its own result carrying
|
|
13
|
+
* an 'info' annotation and a 'parseResult' meta pointing at the result where
|
|
14
|
+
* the document was parsed; that result is also what gets attached to the
|
|
15
|
+
* SourceDescriptionElement's meta. A document referencing one of its ancestors
|
|
16
|
+
* forms a cycle and is skipped with a 'warning' annotation instead.
|
|
17
|
+
*
|
|
11
18
|
* @param parseResult - ParseResult containing an Arazzo specification
|
|
12
19
|
* @param parseResultRetrievalURI - URI from which the parseResult was retrieved
|
|
13
20
|
* @param options - Full ReferenceOptions. Pass `sourceDescriptions` as an array of names
|