@usearazzo/parser 1.0.1-alpha.0 → 1.0.1-alpha.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/README.md CHANGED
@@ -1,716 +1,96 @@
1
- # @usearazzo/parser
1
+ <h1 align="center">@usearazzo/parser</h1>
2
2
 
3
- `@usearazzo/parser` is a parser for the [Arazzo Specification](https://spec.openapis.org/arazzo/latest.html) language.
4
- It covers all three syntaxes the specification defines:
3
+ <p align="center">
4
+ Parser for the <a href="https://spec.openapis.org/arazzo/latest.html">Arazzo Specification</a> language:
5
+ documents, runtime expressions, and criterion conditions.
6
+ </p>
5
7
 
6
- - **Documents**: `parseArazzo` / `parseOpenAPI` produce [SpecLynx ApiDOM](https://github.com/speclynx/apidom) data model using the [Arazzo 1.x namespace](https://github.com/speclynx/apidom/tree/main/packages/apidom-ns-arazzo-1#readme).
7
- - **Runtime expressions** (`$inputs.x`, `$steps.y.outputs.z`, `$response.body#/a/b`, …): `parseRuntimeExpression`.
8
- - **Criterion conditions** (the `simple` criterion grammar): `parseCriterionCondition`.
8
+ <p align="center">
9
+ <a href="https://www.npmjs.com/package/@usearazzo/parser"><img src="https://img.shields.io/npm/v/@usearazzo/parser.svg" alt="npm version"></a>
10
+ <a href="https://github.com/usearazzo/arazzo-toolkit/blob/main/LICENSE"><img src="https://img.shields.io/badge/License-Apache%202.0-blue.svg" alt="License: Apache 2.0"></a>
11
+ </p>
9
12
 
10
- `parseRuntimeExpression` and `parseCriterionCondition` are pure, context-free syntax parsers: a string in, an AST plus position-carrying diagnostics out. They perform no evaluation and require no document; evaluating a parsed expression or condition against runtime state is [@usearazzo/runner](../runner#readme)'s job. [JSONPath](https://datatracker.ietf.org/doc/html/rfc9535) and [JSON Pointer](https://datatracker.ietf.org/doc/html/rfc6901) are general-purpose syntaxes with their own ecosystems and are out of scope for this package. Use `@swaggerexpert/jsonpath` or a JSON Pointer library directly for those.
13
+ <p align="center">
14
+ <a href="https://usearazzo.com"><img alt="UseArazzo" width="96" src="https://usearazzo.com/assets/images/logos/usearazzo-logo.svg"></a>
15
+ </p>
11
16
 
12
- **Supported Arazzo versions:**
17
+ ---
13
18
 
14
- - [Arazzo 1.0.0](https://spec.openapis.org/arazzo/v1.0.0)
15
- - [Arazzo 1.0.1](https://spec.openapis.org/arazzo/v1.0.1)
16
- - [Arazzo 1.1.0](https://spec.openapis.org/arazzo/v1.1.0)
17
-
18
- **Supported OpenAPI versions (for source descriptions):**
19
-
20
- - [OpenAPI 2.0](https://spec.openapis.org/oas/v2.0)
21
- - [OpenAPI 3.0.x](https://spec.openapis.org/oas/v3.0.4)
22
- - [OpenAPI 3.1.x](https://spec.openapis.org/oas/v3.1.2)
23
-
24
- ## Installation
25
-
26
- You can install this package via [npm](https://npmjs.org/) CLI by running the following command:
27
-
28
- ```sh
29
- npm install @usearazzo/parser
30
- ```
31
-
32
- ## Usage
33
-
34
- `@usearazzo/parser` provides `parseArazzo` and `parseOpenAPI` for parsing documents, and `parseRuntimeExpression` / `parseCriterionCondition` for parsing the two embedded Arazzo grammars.
35
-
36
- ## Parsing Arazzo Documents
37
-
38
- The `parseArazzo` function accepts multiple input types:
39
-
40
- 1. **Plain JavaScript object** - converts to JSON and parses (source maps supported with `strict: false`)
41
- 2. **String content** - detects Arazzo content and parses inline JSON or YAML
42
- 3. **File system path** - resolves and parses local Arazzo Documents
43
- 4. **HTTP(S) URL** - fetches and parses remote Arazzo Documents
44
-
45
- ### From object
46
-
47
- ```js
48
- import { parseArazzo } from '@usearazzo/parser';
49
-
50
- const arazzoDocument = {
51
- arazzo: '1.0.1',
52
- info: {
53
- title: 'My API Workflow',
54
- version: '1.0.0',
55
- },
56
- sourceDescriptions: [
57
- {
58
- name: 'myApi',
59
- type: 'openapi',
60
- url: 'https://example.com/openapi.json',
61
- },
62
- ],
63
- workflows: [],
64
- };
65
-
66
- const parseResult = await parseArazzo(arazzoDocument);
67
- // parseResult is ParseResultElement containing ArazzoSpecification1Element
68
- ```
69
-
70
- ### From string
71
-
72
- ```js
73
- import { parseArazzo } from '@usearazzo/parser';
74
-
75
- // JSON string
76
- const parseResult = await parseArazzo('{"arazzo": "1.0.1", "info": {...}}');
77
-
78
- // YAML string
79
- const parseResult = await parseArazzo(`
80
- arazzo: '1.0.1'
81
- info:
82
- title: My API Workflow
83
- version: '1.0.0'
84
- `);
85
- ```
86
-
87
- ### From file
19
+ ## What it parses
88
20
 
89
- ```js
90
- import { parseArazzo } from '@usearazzo/parser';
21
+ An Arazzo document is three languages in one file. This package reads all of them:
91
22
 
92
- const parseResult = await parseArazzo('/path/to/arazzo.json');
93
- ```
94
-
95
- ### From URL
96
-
97
- ```js
98
- import { parseArazzo } from '@usearazzo/parser';
99
-
100
- const parseResult = await parseArazzo('https://example.com/arazzo.yaml');
101
- ```
23
+ - **Documents.** `parseArazzo` and `parseOpenAPI` turn an Arazzo or OpenAPI document, from a file, a URL, a string, or a plain object, into a typed [SpecLynx ApiDOM](https://github.com/speclynx/apidom) tree with optional source positions.
24
+ - **Runtime expressions.** `parseRuntimeExpression` parses `$inputs.x`, `$steps.y.outputs.z`, `$response.body#/a/b`, and friends into an AST.
25
+ - **Criterion conditions.** `parseCriterionCondition` parses the `simple` criterion grammar, such as `$statusCode == 200`, into an AST.
102
26
 
103
- ## Parse options
27
+ The expression and condition parsers are pure syntax parsers: a string in, an AST plus diagnostics out, no evaluation and no document required. Evaluating them against runtime state is [@usearazzo/runner](https://github.com/usearazzo/arazzo-toolkit/tree/main/packages/runner#readme)'s job.
104
28
 
105
- The `parseArazzo` function accepts an optional second argument with reference options compatible with [SpecLynx ApiDOM Reference Options](https://github.com/speclynx/apidom/blob/main/packages/apidom-reference/src/options/index.ts):
106
-
107
- ```js
108
- import { parseArazzo } from '@usearazzo/parser';
109
-
110
- const parseResult = await parseArazzo(source, {
111
- parse: {
112
- parserOpts: {
113
- strict: true, // Use strict parsing mode (default: true)
114
- sourceMap: false, // Include source maps (default: false)
115
- style: false, // Capture style information for round-trip preservation (default: false)
116
- },
117
- },
118
- });
119
- ```
120
-
121
- ### Base URI for inline content
122
-
123
- When parsing from a plain object or inline string, the document has no location of its own, so relative references (such as source description `url`s) have nothing to resolve against. Provide `resolve.baseURI` to tell the parser where the document should be treated as coming from. The in-memory content is still used for the document itself; only relative references are resolved against the base URI, and it is recorded as the `retrievalURI` metadata:
124
-
125
- ```js
126
- import { parseArazzo } from '@usearazzo/parser';
127
-
128
- const arazzoDocument = {
129
- arazzo: '1.0.1',
130
- info: { title: 'My API Workflow', version: '1.0.0' },
131
- sourceDescriptions: [{ name: 'myApi', type: 'openapi', url: './openapi.json' }],
132
- workflows: [],
133
- };
134
-
135
- const parseResult = await parseArazzo(arazzoDocument, {
136
- resolve: { baseURI: '/path/to/arazzo.json' },
137
- parse: { parserOpts: { sourceDescriptions: true } },
138
- });
139
-
140
- parseResult.meta.get('retrievalURI'); // '/path/to/arazzo.json'
141
- parseResult.get(1).meta.get('retrievalURI'); // '/path/to/openapi.json'
142
- ```
29
+ ## At a glance
143
30
 
144
- Relative references are resolved against the base URI the same way as for a file system path or URL passed directly to `parseArazzo`, so it must be absolute; a relative value is not resolved against the working directory. The base URI itself is never read: the in-memory content is always what gets parsed, so it need not exist or have a recognized extension. It is served by the parser's own `memory` resolver, which is placed ahead of any resolvers configured through `resolve.resolvers`. `parseOpenAPI` accepts the option in the same way.
31
+ | Function | Takes | Returns |
32
+ |---|---|---|
33
+ | `parseArazzo` | Arazzo document: path, URL, JSON or YAML string, or object | `ParseResultElement` with a typed `api` tree, `errors`, `warnings` |
34
+ | `parseOpenAPI` | OpenAPI document, same inputs | `ParseResultElement` for OpenAPI 2.0, 3.0.x, or 3.1.x |
35
+ | `parseRuntimeExpression` | Bare expression such as `$steps.findPet.outputs.petId` | `{ result, tree }`, never throws on invalid syntax |
36
+ | `parseCriterionCondition` | `simple` condition such as `$statusCode == 200` | `{ result, tree }`, with each embedded expression parsed too |
145
37
 
146
- ### Default options
38
+ Documents come back as ApiDOM, so source positions for editor tooling, formatting-preserving round trips, and one-call parsing of every source description an Arazzo document points at are all options away.
147
39
 
148
- You can import the default options:
149
-
150
- ```js
151
- import { defaultParseArazzoOptions } from '@usearazzo/parser';
152
- import { defaultParseOpenAPIOptions } from '@usearazzo/parser';
153
-
154
- console.dir(defaultParseArazzoOptions, { depth: null });
155
- console.dir(defaultParseOpenAPIOptions, { depth: null });
156
- ```
157
-
158
- ## Error handling
159
-
160
- When parsing fails, a `ParseError` is thrown (from both `parseArazzo` and `parseOpenAPI`). The original error is available via the `cause` property:
161
-
162
- ```js
163
- import { parseArazzo, ParseError } from '@usearazzo/parser';
164
-
165
- try {
166
- await parseArazzo('invalid content');
167
- } catch (error) {
168
- if (error instanceof ParseError) {
169
- console.error(error.message); // 'Failed to parse Arazzo Document'
170
- console.error(error.cause); // Original error from underlying parser
171
- }
172
- throw error;
173
- }
174
- ```
175
-
176
- ## Working with the result
177
-
178
- The `parseArazzo` function returns a [ParseResultElement](https://github.com/speclynx/apidom/blob/main/packages/apidom-datamodel/README.md#parseresultelement) representing the result of the parsing operation.
179
-
180
- ```js
181
- import { parseArazzo } from '@usearazzo/parser';
182
-
183
- const parseResult = await parseArazzo(source);
184
-
185
- // Access the main Arazzo specification element
186
- const arazzoSpec = parseResult.api;
187
-
188
- // Check if parsing produced any errors
189
- const hasErrors = parseResult.errors.length > 0;
190
-
191
- // Check if parseResult is empty
192
- const isEmpty = parseResult.isEmpty;
193
- ```
194
-
195
- ### Retrieval URI metadata
196
-
197
- When parsing from a file system path or HTTP(S) URL, the `retrievalURI` metadata is set on the parse result:
198
-
199
- ```js
200
- import { parseArazzo } from '@usearazzo/parser';
201
-
202
- const parseResult = await parseArazzo('/path/to/arazzo.json');
203
-
204
- // Get the URI from which the document was retrieved
205
- const uri = parseResult.meta.get('retrievalURI');
206
- // '/path/to/arazzo.json'
207
- ```
208
-
209
- Note: `retrievalURI` is not set when parsing from inline content (string) or plain objects, unless `resolve.baseURI` is provided (see [Base URI for inline content](#base-uri-for-inline-content)).
210
-
211
- ### Source maps
212
-
213
- Source maps allow you to track the original position (line, column) of each element in the parsed document. This is useful for error reporting, IDE integrations, linting, and any tooling that needs to show precise locations in the original source.
214
-
215
- To enable source maps, set `sourceMap: true` and `strict: false` in the parser options:
216
-
217
- ```js
218
- import { parseArazzo } from '@usearazzo/parser';
219
-
220
- const parseResult = await parseArazzo('/path/to/arazzo.yaml', {
221
- parse: {
222
- parserOpts: {
223
- sourceMap: true,
224
- strict: false,
225
- },
226
- },
227
- });
228
- ```
229
-
230
- When source maps are enabled, each element in the parsed result contains positional properties stored directly on the element. Position values use UTF-16 code units for compatibility with Language Server Protocol (LSP) and JavaScript string indexing:
231
-
232
- ```js
233
- import { parseArazzo } from '@usearazzo/parser';
234
-
235
- const parseResult = await parseArazzo('/path/to/arazzo.yaml', {
236
- parse: { parserOpts: { sourceMap: true, strict: false } },
237
- });
238
-
239
- const arazzoSpec = parseResult.api;
240
-
241
- // Access source map properties directly on the element
242
- arazzoSpec.startLine; // 0-based line number where element begins
243
- arazzoSpec.startCharacter; // 0-based column number where element begins
244
- arazzoSpec.startOffset; // 0-based character offset from document start
245
- arazzoSpec.endLine; // 0-based line number where element ends
246
- arazzoSpec.endCharacter; // 0-based column number where element ends
247
- arazzoSpec.endOffset; // 0-based character offset where element ends
248
-
249
- // Access source map on nested elements
250
- const workflow = arazzoSpec.workflows.get(0);
251
- console.log(`Workflow starts at line ${workflow.startLine}, column ${workflow.startCharacter}`);
252
- ```
253
-
254
- For more details about source maps, see the [SpecLynx ApiDOM Data Model documentation](https://github.com/speclynx/apidom/tree/main/packages/apidom-datamodel#source-maps).
255
-
256
- **Note:** Source maps require `strict: false` to be set. When parsing from objects, they are converted to pretty-printed JSON strings internally (2-space indentation), so source map positions refer to this generated JSON representation, not the original object structure:
257
-
258
- ```js
259
- // Source maps with objects (requires strict: false)
260
- // Positions will reference the internally generated JSON string
261
- await parseArazzo({ arazzo: '1.0.1', ... }, {
262
- parse: {
263
- parserOpts: {
264
- sourceMap: true,
265
- strict: false
266
- }
267
- },
268
- });
269
- ```
270
-
271
- ### Style preservation
272
-
273
- Style preservation captures format-specific style information for round-trip preservation. When enabled, the parser records formatting details (e.g., YAML quoting styles, flow/block indicators, comments, indentation; JSON indentation, raw number representation) on each parsed element. These details can then be used by [`toYAML`](https://github.com/speclynx/apidom/tree/main/packages/apidom-core#toyaml) and [`toJSON`](https://github.com/speclynx/apidom/tree/main/packages/apidom-core#tojson) from `@speclynx/apidom-core` to reproduce the original formatting.
274
-
275
- To enable style preservation, set `style: true` and `strict: false` in the parser options:
276
-
277
- ```js
278
- import { parseArazzo } from '@usearazzo/parser';
279
- import { toYAML } from '@speclynx/apidom-core';
280
-
281
- const parseResult = await parseArazzo('/path/to/arazzo.yaml', {
282
- parse: {
283
- parserOpts: {
284
- style: true,
285
- strict: false,
286
- },
287
- },
288
- });
289
-
290
- // round-trip back to YAML preserving original formatting
291
- const yaml = toYAML(parseResult.api, { preserveStyle: true });
292
- ```
40
+ A relative file system path resolves against the current working directory, so `./samples/onboarding.arazzo.yaml` and `path.resolve('samples/onboarding.arazzo.yaml')` are equivalent.
293
41
 
294
- ```js
295
- import { parseArazzo } from '@usearazzo/parser';
296
- import { toJSON } from '@speclynx/apidom-core';
297
-
298
- const parseResult = await parseArazzo('/path/to/arazzo.json', {
299
- parse: {
300
- parserOpts: {
301
- style: true,
302
- strict: false,
303
- },
304
- },
305
- });
306
-
307
- // round-trip back to JSON preserving original formatting
308
- const json = toJSON(parseResult.api, undefined, undefined, { preserveStyle: true });
309
- ```
310
-
311
- **Note:** Style preservation requires `strict: false`. Both `style` and `sourceMap` can be enabled simultaneously — they are independent features.
312
-
313
- ## Parsing source descriptions
314
-
315
- Arazzo documents can reference external API specifications (OpenAPI, Arazzo) through [Source Descriptions](https://spec.openapis.org/arazzo/latest.html#source-description-object). The parser can automatically fetch and parse these referenced documents.
316
-
317
- **Note:** Source descriptions parsing is disabled by default for performance reasons. Enable it explicitly when you need to resolve and parse referenced API specifications.
318
-
319
- **Note:** Relative source description `url`s are resolved against the URI the parent Arazzo document was retrieved from. When the parent is passed as a plain object or inline string without `resolve.baseURI`, that URI is a synthetic `memory://` one, so a relative `url` resolves to a `memory://` URI that no resolver can retrieve. The source description gets an `error` annotation stating this: `Error parsing source description "memory://arazzo.json/openapi.json": relative URL cannot be resolved because the parent document was parsed from inline content. Provide resolve.baseURI or an absolute $self.` Provide `resolve.baseURI` in that case (see [Base URI for inline content](#base-uri-for-inline-content)), use absolute `http(s)://` or `file://` URLs, or declare a `$self` with a scheme (`file://`, `https://`) on the parent document.
320
-
321
- ### Enabling source descriptions parsing
322
-
323
- To parse source descriptions, enable the `sourceDescriptions` option in `parserOpts`:
324
-
325
- ```js
326
- import { parseArazzo } from '@usearazzo/parser';
327
-
328
- const parseResult = await parseArazzo('/path/to/arazzo.json', {
329
- parse: {
330
- parserOpts: {
331
- sourceDescriptions: true,
332
- },
333
- },
334
- });
335
- ```
336
-
337
- Alternatively, you can configure it per parser for more granular control:
338
-
339
- ```js
340
- const parseResult = await parseArazzo('/path/to/arazzo.json', {
341
- parse: {
342
- parserOpts: {
343
- 'arazzo-json-1': { sourceDescriptions: true },
344
- 'arazzo-yaml-1': { sourceDescriptions: true },
345
- },
346
- },
347
- });
348
- ```
349
-
350
- ### Selective parsing
351
-
352
- You can selectively parse only specific source descriptions by providing an array of names:
353
-
354
- ```js
355
- const parseResult = await parseArazzo('/path/to/arazzo.json', {
356
- parse: {
357
- parserOpts: {
358
- sourceDescriptions: ['petStoreApi', 'paymentApi'],
359
- },
360
- },
361
- });
362
- ```
363
-
364
- ### Result structure
365
-
366
- When source descriptions are parsed, each parsed document that is a _direct_ source description of the main Arazzo document is added to the main `ParseResultElement` as an additional top-level element. The first element is always the main Arazzo document, and subsequent top-level elements are these directly parsed source descriptions. When recursive parsing discovers further source descriptions from within an already parsed source description, those recursively parsed documents are attached as nested `ParseResultElement` instances beneath the source-description element that referenced them (they are not duplicated at the top level). Consumers that need to see all documents should traverse both the top-level elements and any nested `ParseResultElement`s reachable from source-description elements.
367
-
368
- Source descriptions are parsed into their appropriate SpecLynx ApiDOM namespace data models based on document type:
369
-
370
- - [Arazzo 1.x](https://spec.openapis.org/arazzo/latest.html) → [@speclynx/apidom-ns-arazzo-1](https://github.com/speclynx/apidom/tree/main/packages/apidom-ns-arazzo-1)
371
- - [OpenAPI 2.0 (Swagger)](https://spec.openapis.org/oas/v2.0) → [@speclynx/apidom-ns-openapi-2](https://github.com/speclynx/apidom/tree/main/packages/apidom-ns-openapi-2)
372
- - [OpenAPI 3.0.x](https://spec.openapis.org/oas/v3.0.4) → [@speclynx/apidom-ns-openapi-3-0](https://github.com/speclynx/apidom/tree/main/packages/apidom-ns-openapi-3-0)
373
- - [OpenAPI 3.1.x](https://spec.openapis.org/oas/v3.1.2) → [@speclynx/apidom-ns-openapi-3-1](https://github.com/speclynx/apidom/tree/main/packages/apidom-ns-openapi-3-1)
374
-
375
- ```
376
- ParseResultElement
377
- ├── .api: ArazzoSpecification1Element
378
- │
379
- ├── ParseResultElement (petStoreApi) ─┐
380
- │ └── .api: OpenApi3_1Element │ source
381
- │ │ descriptions
382
- └── ParseResultElement (legacyApi) ─┘
383
- ├── .errors
384
- └── .warnings
385
- ```
386
-
387
- ```js
388
- import { parseArazzo } from '@usearazzo/parser';
389
- import { toValue } from '@speclynx/apidom-core';
390
-
391
- const parseResult = await parseArazzo('/path/to/arazzo.json', {
392
- parse: {
393
- parserOpts: {
394
- sourceDescriptions: true,
395
- },
396
- },
397
- });
398
-
399
- // Main Arazzo document
400
- const arazzoSpec = parseResult.api;
401
-
402
- // Number of elements (1 main + N source descriptions)
403
- console.log(parseResult.length);
404
-
405
- // Access parsed source descriptions (filter by 'source-description' class)
406
- for (let i = 0; i < parseResult.length; i++) {
407
- const element = parseResult.get(i);
408
-
409
- if (element.classes.includes('source-description')) {
410
- // Source description metadata
411
- const name = toValue(element.meta.get('name'));
412
- const type = toValue(element.meta.get('type'));
413
-
414
- console.log(`Source description "${name}" (${type})`);
42
+ ## Installation
415
43
 
416
- // The parsed API document
417
- const api = element.api;
418
- }
419
- }
44
+ ```sh
45
+ npm install @usearazzo/parser
420
46
  ```
421
47
 
422
- ### Accessing via SourceDescriptionElement
48
+ Ships ESM and CommonJS builds with TypeScript declarations. Requires Node.js 20.10 or newer.
423
49
 
424
- An alternative way to access parsed source descriptions is through the `SourceDescriptionElement` metadata.
425
- When source descriptions are parsed, a `ParseResultElement` is attached to each `SourceDescriptionElement`'s metadata under the key `'parseResult'`.
50
+ ## Usage
426
51
 
427
52
  ```js
428
53
  import { parseArazzo } from '@usearazzo/parser';
429
54
  import { toValue } from '@speclynx/apidom-core';
430
55
 
431
- const parseResult = await parseArazzo('/path/to/arazzo.json', {
432
- parse: {
433
- parserOpts: {
434
- sourceDescriptions: true,
435
- },
436
- },
437
- });
438
-
439
- const arazzoSpec = parseResult.api;
440
-
441
- // Access parsed document via SourceDescriptionElement
442
- const sourceDesc = arazzoSpec.sourceDescriptions.get(0);
443
- const sdParseResult = sourceDesc.meta.get('parseResult');
444
-
445
- // Check for errors before using
446
- if (sdParseResult.errors.length === 0) {
447
- // Access the parsed API
448
- const api = sdParseResult.api;
449
- console.log(`API type: ${api.element}`); // e.g., 'openApi3_1'
450
-
451
- // Get the retrieval URI
452
- const retrievalURI = sdParseResult.meta.get('retrievalURI');
453
- console.log(`Loaded from: ${retrievalURI}`);
454
- }
455
- ```
456
-
457
- This approach is useful when you need to:
458
-
459
- - Access a specific source description by its position in the `sourceDescriptions` array
460
- - Get the `retrievalURI` metadata indicating where the document was fetched from
461
- - Correlate parsed documents with their source description definitions
462
-
463
- ### Recursive parsing
464
-
465
- When a source description is of type `arazzo`, the parser recursively parses that document's source descriptions as well. This allows you to parse entire dependency trees of Arazzo documents.
466
-
467
- ### Limiting recursion depth
468
-
469
- To prevent excessive recursion or handle deeply nested documents, use the `sourceDescriptionsMaxDepth` option:
470
-
471
- ```js
472
- const parseResult = await parseArazzo('/path/to/arazzo.json', {
473
- parse: {
474
- parserOpts: {
475
- sourceDescriptions: true,
476
- sourceDescriptionsMaxDepth: 2, // Only parse 2 levels deep
477
- },
478
- },
479
- });
480
- ```
481
-
482
- The default value is `+Infinity` (no limit). Setting it to `0` will create error annotations instead of parsing any source descriptions.
483
-
484
- ### Cycle detection
56
+ const parseResult = await parseArazzo('./adopt-a-pet.arazzo.yaml');
485
57
 
486
- The parser automatically detects circular references between Arazzo documents. When a cycle is detected, a warning annotation is added instead of causing infinite recursion:
487
-
488
- ```js
489
- // arazzo-a.json references arazzo-b.json
490
- // arazzo-b.json references arazzo-a.json (cycle!)
491
-
492
- const parseResult = await parseArazzo('/path/to/arazzo-a.json', {
493
- parse: {
494
- parserOpts: {
495
- sourceDescriptions: true,
496
- },
497
- },
498
- });
499
-
500
- // The cycle is handled gracefully - check for warning annotations
58
+ parseResult.errors.length; // 0
59
+ toValue(parseResult.api.info.title); // 'Pet adoption'
60
+ toValue(parseResult.api.workflows.get(0).workflowId); // 'adopt-a-pet'
61
+ parseResult.api.workflows.get(0).steps.length; // 2
501
62
  ```
502
63
 
503
- Reaching the same document a second time through a different path is not a cycle. A document shared by several
504
- Arazzo documents (typically one OpenAPI description used by every workflow document) is parsed only once, and every
505
- later source description referencing it gets a `ParseResultElement` that has no `api` of its own, carries an `info`
506
- annotation, and points at the `ParseResultElement` where the document was parsed via its `'parseResult'` metadata.
507
- The `SourceDescriptionElement`'s `'parseResult'` metadata points at that same `ParseResultElement`:
64
+ The two grammar parsers are synchronous and need no document:
508
65
 
509
66
  ```js
510
- // arazzo-a.json references openapi.json and arazzo-b.json
511
- // arazzo-b.json references openapi.json as well (shared, not a cycle)
512
-
513
- const parseResult = await parseArazzo('/path/to/arazzo-a.json', {
514
- parse: {
515
- parserOpts: {
516
- sourceDescriptions: true,
517
- },
518
- },
519
- });
520
-
521
- const arazzoB = parseResult.get(2);
522
- const sharedOpenApi = arazzoB.api.sourceDescriptions.get(0).meta.get('parseResult');
523
- sharedOpenApi === parseResult.get(1); // true; the OpenAPI document parsed for arazzo-a.json
524
- ```
525
-
526
- ### Error and warning handling
67
+ import { parseRuntimeExpression, parseCriterionCondition } from '@usearazzo/parser';
527
68
 
528
- When issues occur during source description parsing, the parser does not throw errors. Instead, it adds annotation elements to the source description's parse result:
69
+ parseRuntimeExpression('$steps.findPet.outputs.petId').tree;
70
+ // { type: 'StepsExpression', stepId: 'findPet', field: 'outputs', outputName: 'petId' }
529
71
 
530
- - **`error`** class - Parsing failed (e.g., file not found, invalid document, max depth exceeded)
531
- - **`warning`** class - Non-fatal issues (e.g., cycle detected, type mismatch between declared and actual)
532
- - **`info`** class - Nothing went wrong (e.g., a shared document was reused instead of being parsed again)
533
-
534
- This allows partial parsing to succeed even if some source descriptions have issues:
535
-
536
- ```js
537
- const parseResult = await parseArazzo('/path/to/arazzo.json', {
538
- parse: {
539
- parserOpts: {
540
- sourceDescriptions: true,
541
- },
542
- },
543
- });
544
-
545
- // Check each source description for errors and warnings
546
- for (let i = 0; i < parseResult.length; i++) {
547
- const element = parseResult.get(i);
548
-
549
- if (element.classes.includes('source-description')) {
550
- const name = toValue(element.meta.get('name'));
551
-
552
- // Use built-in accessors for errors and warnings
553
- element.errors.forEach((error) => {
554
- console.error(`Error in "${name}": ${toValue(error)}`);
555
- });
556
-
557
- element.warnings.forEach((warning) => {
558
- console.warn(`Warning in "${name}": ${toValue(warning)}`);
559
- });
560
- }
561
- }
72
+ parseCriterionCondition('$statusCode == 200').result.success; // true
73
+ parseCriterionCondition('$statusCode ===').result.success; // false, and nothing thrown
562
74
  ```
563
75
 
564
- ## Parsing OpenAPI Documents
565
-
566
- The `parseOpenAPI` function provides complete control for parsing OpenAPI documents manually.
567
- This is useful when you need to parse source descriptions independently or implement custom source description resolution logic.
568
-
569
- The function accepts the same input types as `parseArazzo`:
570
-
571
- 1. **Plain JavaScript object** - converts to JSON and parses
572
- 2. **String content** - detects OpenAPI content and parses inline JSON or YAML
573
- 3. **File system path** - resolves and parses local OpenAPI Documents
574
- 4. **HTTP(S) URL** - fetches and parses remote OpenAPI Documents
575
-
576
- Documents are parsed into their appropriate SpecLynx ApiDOM namespace data models:
577
-
578
- - [OpenAPI 2.0 (Swagger)](https://spec.openapis.org/oas/v2.0) → [@speclynx/apidom-ns-openapi-2](https://github.com/speclynx/apidom/tree/main/packages/apidom-ns-openapi-2)
579
- - [OpenAPI 3.0.x](https://spec.openapis.org/oas/v3.0.4) → [@speclynx/apidom-ns-openapi-3-0](https://github.com/speclynx/apidom/tree/main/packages/apidom-ns-openapi-3-0)
580
- - [OpenAPI 3.1.x](https://spec.openapis.org/oas/v3.1.2) → [@speclynx/apidom-ns-openapi-3-1](https://github.com/speclynx/apidom/tree/main/packages/apidom-ns-openapi-3-1)
581
-
582
- ```js
583
- import { parseOpenAPI } from '@usearazzo/parser';
584
-
585
- // From object
586
- const parseResult = await parseOpenAPI({
587
- openapi: '3.1.0',
588
- info: { title: 'My API', version: '1.0.0' },
589
- paths: {},
590
- });
76
+ Everything else, from parse options and source maps to parsing source descriptions and error handling, is in the API reference:
591
77
 
592
- // From string
593
- const parseResult = await parseOpenAPI('{"openapi": "3.1.0", ...}');
594
-
595
- // From file
596
- const parseResult = await parseOpenAPI('/path/to/openapi.json');
597
-
598
- // From URL
599
- const parseResult = await parseOpenAPI('https://example.com/openapi.yaml');
600
- ```
601
-
602
- ## Parsing Runtime Expressions
603
-
604
- The `parseRuntimeExpression` function parses an [Arazzo Runtime Expression](https://spec.openapis.org/arazzo/latest.html#runtime-expressions) into its AST. The expression must be the bare form without surrounding braces (e.g. `$inputs.username`, not `{$inputs.username}`). Invalid syntax never throws: it is reported through the returned `result`, so the function is safe to use as a pure syntax check (e.g. for linting expressions embedded in a document) without any evaluation context.
605
-
606
- ```js
607
- import { parseRuntimeExpression } from '@usearazzo/parser';
608
-
609
- // Valid expression
610
- const { result, tree } = parseRuntimeExpression('$steps.myStep.outputs.result');
611
- // result.success === true
612
- // tree === { type: 'StepsExpression', stepId: 'myStep', field: 'outputs', outputName: 'result' }
613
-
614
- // Invalid expression
615
- const invalid = parseRuntimeExpression('$unknown.thing');
616
- // invalid.result.success === false
617
- // invalid.tree === undefined
618
- // invalid.result.maxMatched - offset into the string parsing succeeded up to
619
- ```
620
-
621
- `parseRuntimeExpression` is a thin wrapper around [@swaggerexpert/arazzo-runtime-expression](https://www.npmjs.com/package/@swaggerexpert/arazzo-runtime-expression), which is re-exported as a foundation dependency: `ASTNode` (exported here as `RuntimeExpressionASTNode`) and `ParseResult` (`ParseRuntimeExpressionResult`) are its types, following the same pattern as the ApiDOM `ParseResultElement` re-export above. Splitting a `{expression}` template into literal and expression spans, or interpolating one, is left to that package's own `extract` / `interpolate` functions, outside this package's document/expression/condition parsing surface.
622
-
623
- `parseRuntimeExpression` does throw in two cases, both re-exported (or standard) so they can be caught by type:
624
-
625
- - `TypeError` when `expression` is not a string.
626
- - `ArazzoRuntimeExpressionParseError` on an unexpected internal parser error, distinct from invalid syntax (which never throws).
627
-
628
- ```js
629
- import { parseRuntimeExpression, ArazzoRuntimeExpressionParseError } from '@usearazzo/parser';
630
-
631
- try {
632
- parseRuntimeExpression(expression);
633
- } catch (error) {
634
- if (error instanceof ArazzoRuntimeExpressionParseError) {
635
- console.error('Unexpected parser error:', error.cause);
636
- }
637
- throw error;
638
- }
639
- ```
78
+ **[API reference](https://usearazzo.com/docs/parser/)** &middot; **[Parsing Arazzo Documents guide](https://usearazzo.com/docs/guides/arazzo-document-parsing/)**
640
79
 
641
- ## Parsing Criterion Conditions
80
+ ## Supported versions
642
81
 
643
- The `parseCriterionCondition` function parses the [Criterion Object](https://spec.openapis.org/arazzo/latest.html#criterion-object)'s `simple` condition grammar into its AST. Like `parseRuntimeExpression`, invalid syntax never throws and is reported through the returned `result`. Each embedded runtime expression operand is parsed too, carried on its `RuntimeExpression` AST node as a `parseRuntimeExpression`-produced sub-AST.
82
+ Arazzo documents:
644
83
 
645
- ```js
646
- import { parseCriterionCondition } from '@usearazzo/parser';
647
-
648
- // Valid condition
649
- const { result, tree } = parseCriterionCondition('$statusCode == 200');
650
- // result.success === true
651
- // tree.type === 'BinaryExpression'
652
-
653
- // Invalid condition
654
- const invalid = parseCriterionCondition('$statusCode ===');
655
- // invalid.result.success === false
656
- // invalid.tree === undefined
657
- ```
658
-
659
- `parseCriterionCondition` wraps [@swaggerexpert/arazzo-criterion](https://www.npmjs.com/package/@swaggerexpert/arazzo-criterion) the same way: `ConditionAST` (`CriterionConditionAST`) and `ParseResult` (`ParseCriterionConditionResult`) are re-exported foundation types, and it throws the same two ways: `TypeError` for a non-string `condition`, and a re-exported `ArazzoCriterionParseError` for an unexpected internal parser error.
660
-
661
- ## SpecLynx ApiDOM tooling
662
-
663
- Since `@usearazzo/parser` produces a SpecLynx ApiDOM data model, you have access to the full suite of ApiDOM tools for manipulating, traversing, and transforming the parsed document.
664
-
665
- ### Core utilities
666
-
667
- The [@speclynx/apidom-core](https://github.com/speclynx/apidom/tree/main/packages/apidom-core) package provides essential utilities for working with ApiDOM elements. Here are just a few examples:
668
-
669
- ```js
670
- import { parseArazzo } from '@usearazzo/parser';
671
- import { cloneDeep, cloneShallow } from '@speclynx/apidom-datamodel';
672
- import { toValue, toJSON, toYAML, sexprs } from '@speclynx/apidom-core';
673
-
674
- const parseResult = await parseArazzo(source);
675
- const arazzoSpec = parseResult.api;
676
-
677
- // Convert to plain JavaScript object
678
- const obj = toValue(arazzoSpec);
679
-
680
- // Serialize to JSON string
681
- const json = toJSON(arazzoSpec);
682
-
683
- // Serialize to YAML string
684
- const yaml = toYAML(arazzoSpec);
685
-
686
- // Clone the element
687
- const clonedShallow = cloneShallow(arazzoSpec);
688
- const clonedDeep = cloneDeep(arazzoSpec);
689
-
690
- // Get S-expression representation (useful for debugging)
691
- const sexpr = sexprs(arazzoSpec);
692
- ```
84
+ - [Arazzo 1.0.0](https://spec.openapis.org/arazzo/v1.0.0)
85
+ - [Arazzo 1.0.1](https://spec.openapis.org/arazzo/v1.0.1)
86
+ - [Arazzo 1.1.0](https://spec.openapis.org/arazzo/v1.1.0)
693
87
 
694
- ### Traversal
88
+ OpenAPI documents, as source descriptions or on their own:
695
89
 
696
- The [@speclynx/apidom-traverse](https://github.com/speclynx/apidom/tree/main/packages/apidom-traverse) package provides powerful traversal capabilities. Here is a basic example:
90
+ - [OpenAPI 2.0](https://spec.openapis.org/oas/v2.0)
91
+ - [OpenAPI 3.0.x](https://spec.openapis.org/oas/v3.0.4)
92
+ - [OpenAPI 3.1.x](https://spec.openapis.org/oas/v3.1.2)
697
93
 
698
- ```js
699
- import { parseArazzo } from '@usearazzo/parser';
700
- import { traverse } from '@speclynx/apidom-traverse';
701
-
702
- const parseResult = await parseArazzo(source);
703
-
704
- // Traverse and collect steps using semantic visitor hook
705
- const steps = [];
706
- traverse(parseResult.api, {
707
- StepElement(path) {
708
- steps.push(path.node);
709
- if (steps.length >= 10) {
710
- path.stop(); // Stop traversal after collecting 10 steps
711
- }
712
- },
713
- });
714
- ```
94
+ ## Part of the Arazzo Toolkit
715
95
 
716
- For more information about available utilities, see the [SpecLynx ApiDOM documentation](https://github.com/speclynx/apidom).
96
+ `@usearazzo/parser` is the reading layer of the [arazzo-toolkit](https://github.com/usearazzo/arazzo-toolkit) monorepo, beside [@usearazzo/resolver](https://github.com/usearazzo/arazzo-toolkit/tree/main/packages/resolver#readme), [@usearazzo/validator](https://github.com/usearazzo/arazzo-toolkit/tree/main/packages/validator#readme), and [@usearazzo/runner](https://github.com/usearazzo/arazzo-toolkit/tree/main/packages/runner#readme). Questions and ideas go to [GitHub Discussions](https://github.com/orgs/usearazzo/discussions).