Skip to content

Commit df78cb3

Browse files
authored
feat(reference): add retrievalURI metadata for Arazzo Source Descriptions (#79)
1 parent 171956d commit df78cb3

File tree

7 files changed

+95
-7
lines changed

7 files changed

+95
-7
lines changed

packages/apidom-reference/README.md

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -301,7 +301,7 @@ Parser-specific options take precedence over global options.
301301
- `true` - parse all source descriptions
302302
- `string[]` - parse only source descriptions with matching names (e.g., `['petStore', 'paymentApi']`)
303303

304-
Each parsed source description is added with a `'source-description'` class and metadata (`name`, `type`).
304+
Each parsed source description is added with a `'source-description'` class and metadata (`name`, `type`, `retrievalURI`).
305305
Only OpenAPI 2.0, OpenAPI 3.0.x, OpenAPI 3.1.x, and Arazzo 1.x documents are accepted as source descriptions.
306306
- **sourceDescriptionsMaxDepth** - Maximum recursion depth for parsing nested Arazzo source descriptions.
307307
Defaults to `+Infinity`. Circular references are automatically detected and skipped.
@@ -321,6 +321,7 @@ with an `'error'` class within the parse result:
321321
for parsing are not met (e.g., API is not an Arazzo specification)
322322

323323
```js
324+
import { toValue } from '@speclynx/apidom-core';
324325
import { parse } from '@speclynx/apidom-reference';
325326

326327
// Parse all source descriptions
@@ -345,8 +346,9 @@ const parseResultFiltered = await parse('/path/to/arazzo.json', {
345346
// Access parsed source descriptions
346347
for (const element of parseResult) {
347348
if (element.classes.includes('source-description')) {
348-
console.log(element.meta.get('name').toValue()); // e.g., 'petStore'
349-
console.log(element.meta.get('type').toValue()); // e.g., 'openapi'
349+
console.log(toValue(element.meta.get('name'))); // e.g., 'petStore'
350+
console.log(toValue(element.meta.get('type'))); // e.g., 'openapi'
351+
console.log(toValue(element.meta.get('retrievalURI'))); // e.g., '/path/to/petstore.json'
350352
}
351353
}
352354
```
@@ -361,6 +363,7 @@ regardless of success or failure:
361363
- **On failure**: The parseResult contains error/warning annotations explaining what went wrong
362364

363365
```js
366+
import { toValue } from '@speclynx/apidom-core';
364367
import { parse } from '@speclynx/apidom-reference';
365368

366369
const parseResult = await parse('/path/to/arazzo.json', {
@@ -377,6 +380,7 @@ if (parsedDoc.errors.length > 0) {
377380
console.log('Parsing failed:', parsedDoc.errors);
378381
} else {
379382
console.log(parsedDoc.api.element); // e.g., 'openApi3_1'
383+
console.log(toValue(parsedDoc.meta.get('retrievalURI'))); // URI where it was fetched from
380384
}
381385
```
382386

@@ -450,7 +454,7 @@ Parser-specific options take precedence over global options. See [arazzo-json-1]
450454
- `true` - parse all source descriptions
451455
- `string[]` - parse only source descriptions with matching names (e.g., `['petStore', 'paymentApi']`)
452456

453-
Each parsed source description is added with a `'source-description'` class and metadata (`name`, `type`).
457+
Each parsed source description is added with a `'source-description'` class and metadata (`name`, `type`, `retrievalURI`).
454458
Only OpenAPI 2.0, OpenAPI 3.0.x, OpenAPI 3.1.x, and Arazzo 1.x documents are accepted as source descriptions.
455459
- **sourceDescriptionsMaxDepth** - Maximum recursion depth for parsing nested Arazzo source descriptions.
456460
Defaults to `+Infinity`. Circular references are automatically detected and skipped.
@@ -1756,7 +1760,7 @@ Strategy-specific options take precedence over global options.
17561760
- `true` - dereference all source descriptions
17571761
- `string[]` - dereference only source descriptions with matching names (e.g., `['petStore', 'paymentApi']`)
17581762

1759-
Each dereferenced source description is added with a `'source-description'` class and metadata (`name`, `type`).
1763+
Each dereferenced source description is added with a `'source-description'` class and metadata (`name`, `type`, `retrievalURI`).
17601764
Only OpenAPI 2.0, OpenAPI 3.0.x, OpenAPI 3.1.x, and Arazzo 1.x documents are accepted as source descriptions.
17611765
- **sourceDescriptionsMaxDepth** - Maximum recursion depth for dereferencing nested Arazzo source descriptions.
17621766
Defaults to `+Infinity`. Circular references are automatically detected and skipped.
@@ -1810,6 +1814,7 @@ regardless of success or failure:
18101814
- **On failure**: The parseResult contains error/warning annotations explaining what went wrong
18111815

18121816
```js
1817+
import { toValue } from '@speclynx/apidom-core';
18131818
import { dereference } from '@speclynx/apidom-reference';
18141819

18151820
const parseResult = await dereference('/path/to/arazzo.json', {
@@ -1826,6 +1831,7 @@ if (dereferencedDoc.errors.length > 0) {
18261831
console.log('Dereferencing failed:', dereferencedDoc.errors);
18271832
} else {
18281833
console.log(dereferencedDoc.api.element); // e.g., 'openApi3_1'
1834+
console.log(toValue(dereferencedDoc.meta.get('retrievalURI'))); // URI where it was fetched from
18291835
}
18301836
```
18311837

packages/apidom-reference/src/dereference/strategies/arazzo-1/source-descriptions.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ async function dereferenceSourceDescription(
6767

6868
// normalize URI for consistent cycle detection and refSet cache key matching
6969
const retrievalURI = url.sanitize(url.stripHash(url.resolve(ctx.baseURI, sourceDescriptionURI)));
70+
parseResult.setMetaProperty('retrievalURI', retrievalURI);
7071

7172
// skip if already visited (cycle detection)
7273
if (ctx.visitedUrls.has(retrievalURI)) {
@@ -199,7 +200,7 @@ async function dereferenceSourceDescription(
199200
* in `dereference.strategyOpts` to filter which source descriptions to process.
200201
* @param strategyName - Strategy name for options lookup (defaults to 'arazzo-1')
201202
* @returns Array of ParseResultElements. Returns one ParseResultElement per source description
202-
* (each with class 'source-description' and name/type metadata).
203+
* (each with class 'source-description' and metadata: name, type, retrievalURI).
203204
* May return early with a single-element array containing a warning annotation when:
204205
* - The API is not an Arazzo specification
205206
* - The sourceDescriptions field is missing or not an array
@@ -208,6 +209,8 @@ async function dereferenceSourceDescription(
208209
*
209210
* @example
210211
* ```typescript
212+
* import { toValue } from '@speclynx/apidom-core';
213+
*
211214
* // Dereference all source descriptions
212215
* await dereferenceSourceDescriptions(parseResult, uri, options);
213216
*
@@ -219,6 +222,7 @@ async function dereferenceSourceDescription(
219222
* // Access dereferenced document from source description element
220223
* const sourceDesc = parseResult.api.sourceDescriptions.get(0);
221224
* const dereferencedDoc = sourceDesc.meta.get('parseResult');
225+
* const retrievalURI = toValue(dereferencedDoc.meta.get('retrievalURI'));
222226
* ```
223227
*
224228
* @public

packages/apidom-reference/src/parse/parsers/arazzo-json-1/source-descriptions.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ async function parseSourceDescription(
6969

7070
// normalize URI for consistent cycle detection and cache key matching
7171
const retrievalURI = url.sanitize(url.stripHash(url.resolve(ctx.baseURI, sourceDescriptionURI)));
72+
parseResult.setMetaProperty('retrievalURI', retrievalURI);
7273

7374
// skip if already visited (cycle detection)
7475
if (ctx.visitedUrls.has(retrievalURI)) {
@@ -166,7 +167,7 @@ async function parseSourceDescription(
166167
* in `parse.parserOpts` to filter which source descriptions to process.
167168
* @param parserName - Parser name for options lookup (defaults to 'arazzo-json-1')
168169
* @returns Array of ParseResultElements. Returns one ParseResultElement per source description
169-
* (each with class 'source-description' and name/type metadata).
170+
* (each with class 'source-description' and metadata: name, type, retrievalURI).
170171
* May return early with a single-element array containing a warning annotation when:
171172
* - The API is not an Arazzo specification
172173
* - The sourceDescriptions field is missing or not an array
@@ -175,6 +176,7 @@ async function parseSourceDescription(
175176
*
176177
* @example
177178
* ```typescript
179+
* import { toValue } from '@speclynx/apidom-core';
178180
* import { options, mergeOptions } from '@speclynx/apidom-reference';
179181
* import { parseSourceDescriptions } from '@speclynx/apidom-reference/parse/parsers/arazzo-json-1';
180182
*
@@ -189,6 +191,7 @@ async function parseSourceDescription(
189191
* // Access parsed document from source description element
190192
* const sourceDesc = parseResult.api.sourceDescriptions.get(0);
191193
* const parsedDoc = sourceDesc.meta.get('parseResult');
194+
* const retrievalURI = toValue(parsedDoc.meta.get('retrievalURI'));
192195
* ```
193196
*
194197
* @public

packages/apidom-reference/test/dereference/strategies/arazzo-1/source-descriptions/index.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,27 @@ describe('dereference', function () {
3434
assert.strictEqual(sdResult.meta.get('name')!.toValue(), 'petStore');
3535
assert.strictEqual(sdResult.meta.get('type')!.toValue(), 'openapi');
3636
});
37+
38+
specify(
39+
'should set retrievalURI metadata on source description result',
40+
async function () {
41+
const uri = path.join(rootFixturePath, 'root.json');
42+
const dereferenceResult = await dereference(uri, {
43+
parse: { mediaType: mediaTypes.latest('json') },
44+
dereference: {
45+
strategyOpts: {
46+
'arazzo-1': { sourceDescriptions: true },
47+
},
48+
},
49+
});
50+
51+
const sdResult = dereferenceResult.get(1)!;
52+
const retrievalURI = sdResult.meta.get('retrievalURI')?.toValue();
53+
54+
assert.isString(retrievalURI);
55+
assert.include(retrievalURI, 'openapi.json');
56+
},
57+
);
3758
});
3859

3960
context('given sourceDescriptions disabled', function () {

packages/apidom-reference/test/parse/parsers/arazzo-json-1/index.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,26 @@ describe('parsers', function () {
222222
assert.strictEqual(sdParseResult.meta.get('name')!.toValue(), 'petStore');
223223
assert.strictEqual(sdParseResult.meta.get('type')!.toValue(), 'openapi');
224224
});
225+
226+
specify(
227+
'should set retrievalURI metadata on source description result',
228+
async function () {
229+
const uri = path.join(__dirname, 'fixtures', 'source-descriptions', 'root.json');
230+
const parseResult = await parse(uri, {
231+
parse: {
232+
parserOpts: {
233+
'arazzo-json-1': { sourceDescriptions: true },
234+
},
235+
},
236+
});
237+
238+
const sdParseResult = parseResult.get(1)!;
239+
const retrievalURI = sdParseResult.meta.get('retrievalURI')?.toValue();
240+
241+
assert.isString(retrievalURI);
242+
assert.include(retrievalURI, 'openapi.json');
243+
},
244+
);
225245
});
226246

227247
context('given sourceDescriptions disabled', function () {

packages/apidom-reference/test/parse/parsers/arazzo-json-1/source-descriptions.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,20 @@ describe('parsers', function () {
143143
assert.isTrue(isParseResultElement(attachedParseResult));
144144
assert.strictEqual(attachedParseResult.api?.element, 'openApi3_1');
145145
});
146+
147+
specify('should set retrievalURI metadata on source description result', async function () {
148+
const uri = path.join(__dirname, 'fixtures', 'source-descriptions', 'root.json');
149+
const data = fs.readFileSync(uri).toString();
150+
const parseResult = await parse(data);
151+
152+
const sourceDescriptions = await parseSourceDescriptions(parseResult, uri, options);
153+
154+
const sdParseResult = sourceDescriptions[0]!;
155+
const retrievalURI = sdParseResult.meta.get('retrievalURI')?.toValue();
156+
157+
assert.isString(retrievalURI);
158+
assert.include(retrievalURI, 'openapi.json');
159+
});
146160
});
147161
});
148162
});

packages/apidom-reference/test/parse/parsers/arazzo-yaml-1/index.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,26 @@ describe('parsers', function () {
249249
assert.strictEqual(sdParseResult.meta.get('name')!.toValue(), 'petStore');
250250
assert.strictEqual(sdParseResult.meta.get('type')!.toValue(), 'openapi');
251251
});
252+
253+
specify(
254+
'should set retrievalURI metadata on source description result',
255+
async function () {
256+
const uri = path.join(__dirname, 'fixtures', 'source-descriptions', 'root.yaml');
257+
const parseResult = await parse(uri, {
258+
parse: {
259+
parserOpts: {
260+
'arazzo-yaml-1': { sourceDescriptions: true },
261+
},
262+
},
263+
});
264+
265+
const sdParseResult = parseResult.get(1)!;
266+
const retrievalURI = sdParseResult.meta.get('retrievalURI')?.toValue();
267+
268+
assert.isString(retrievalURI);
269+
assert.include(retrievalURI, 'openapi.yaml');
270+
},
271+
);
252272
});
253273

254274
context('given sourceDescriptions disabled', function () {

0 commit comments

Comments
 (0)