@usearazzo/parser 1.0.1-alpha.0 → 1.0.1-alpha.10
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 +73 -0
- package/README.md +59 -676
- package/dist/arazzo-parser.browser.js +1778 -1980
- package/dist/arazzo-parser.browser.min.js +1 -1
- package/package.json +22 -19
- package/src/parse-arazzo.cjs +16 -2
- package/src/parse-arazzo.mjs +16 -2
- package/src/parse-openapi.cjs +14 -2
- package/src/parse-openapi.mjs +15 -3
package/README.md
CHANGED
|
@@ -1,716 +1,99 @@
|
|
|
1
|
-
|
|
1
|
+
<h1 align="center">@usearazzo/parser</h1>
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
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
|
-
|
|
7
|
-
|
|
8
|
-
-
|
|
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
|
-
|
|
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
|
-
|
|
17
|
+
---
|
|
13
18
|
|
|
14
|
-
|
|
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
|
|
88
|
-
|
|
89
|
-
```js
|
|
90
|
-
import { parseArazzo } from '@usearazzo/parser';
|
|
91
|
-
|
|
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
|
-
```
|
|
102
|
-
|
|
103
|
-
## Parse options
|
|
104
|
-
|
|
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
|
-
```
|
|
143
|
-
|
|
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.
|
|
145
|
-
|
|
146
|
-
### Default options
|
|
147
|
-
|
|
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
|
-
```
|
|
293
|
-
|
|
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.
|
|
19
|
+
## What it parses
|
|
316
20
|
|
|
317
|
-
|
|
21
|
+
An Arazzo document is three languages in one file. This package reads all of them:
|
|
318
22
|
|
|
319
|
-
**
|
|
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.
|
|
320
26
|
|
|
321
|
-
|
|
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.
|
|
322
28
|
|
|
323
|
-
|
|
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
|
-
```
|
|
29
|
+
## At a glance
|
|
363
30
|
|
|
364
|
-
|
|
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 |
|
|
365
37
|
|
|
366
|
-
|
|
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.
|
|
367
39
|
|
|
368
|
-
|
|
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.
|
|
369
41
|
|
|
370
|
-
|
|
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
|
-
|
|
417
|
-
|
|
418
|
-
}
|
|
419
|
-
}
|
|
44
|
+
```sh
|
|
45
|
+
npm install @usearazzo/parser
|
|
420
46
|
```
|
|
421
47
|
|
|
422
|
-
|
|
48
|
+
Ships ESM and CommonJS builds with TypeScript declarations. Requires Node.js 20.10 or newer.
|
|
423
49
|
|
|
424
|
-
|
|
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('
|
|
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.
|
|
56
|
+
const parseResult = await parseArazzo('./adopt-a-pet.arazzo.yaml');
|
|
483
57
|
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
```
|
|
67
|
+
import { parseRuntimeExpression, parseCriterionCondition } from '@usearazzo/parser';
|
|
525
68
|
|
|
526
|
-
|
|
69
|
+
parseRuntimeExpression('$steps.findPet.outputs.petId').tree;
|
|
70
|
+
// { type: 'StepsExpression', stepId: 'findPet', field: 'outputs', outputName: 'petId' }
|
|
527
71
|
|
|
528
|
-
|
|
529
|
-
|
|
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
|
-
##
|
|
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';
|
|
76
|
+
## Documentation
|
|
584
77
|
|
|
585
|
-
|
|
586
|
-
const parseResult = await parseOpenAPI({
|
|
587
|
-
openapi: '3.1.0',
|
|
588
|
-
info: { title: 'My API', version: '1.0.0' },
|
|
589
|
-
paths: {},
|
|
590
|
-
});
|
|
78
|
+
The examples above are the happy path. Everything else is in the docs at [usearazzo.com/docs](https://usearazzo.com/docs/):
|
|
591
79
|
|
|
592
|
-
|
|
593
|
-
|
|
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
|
-
```
|
|
80
|
+
- **[Parser API Reference](https://usearazzo.com/docs/parser/)** covers every exported function: its options, from source maps to parsing source descriptions, the shape of what it returns, and the errors it can throw. Start here when you know what you want to call.
|
|
81
|
+
- **[Parsing Arazzo Documents](https://usearazzo.com/docs/guides/arazzo-document-parsing/)** explains what makes an Arazzo document hard to parse, what a parsed document should look like, and what a good parser gives you. Start here when you want to understand why the package is shaped the way it is.
|
|
640
82
|
|
|
641
|
-
##
|
|
83
|
+
## Supported versions
|
|
642
84
|
|
|
643
|
-
|
|
85
|
+
Arazzo documents:
|
|
644
86
|
|
|
645
|
-
|
|
646
|
-
|
|
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
|
-
```
|
|
87
|
+
- [Arazzo 1.0.0](https://spec.openapis.org/arazzo/v1.0.0)
|
|
88
|
+
- [Arazzo 1.0.1](https://spec.openapis.org/arazzo/v1.0.1)
|
|
89
|
+
- [Arazzo 1.1.0](https://spec.openapis.org/arazzo/v1.1.0)
|
|
693
90
|
|
|
694
|
-
|
|
91
|
+
OpenAPI documents, as source descriptions or on their own:
|
|
695
92
|
|
|
696
|
-
|
|
93
|
+
- [OpenAPI 2.0](https://spec.openapis.org/oas/v2.0)
|
|
94
|
+
- [OpenAPI 3.0.x](https://spec.openapis.org/oas/v3.0.4)
|
|
95
|
+
- [OpenAPI 3.1.x](https://spec.openapis.org/oas/v3.1.2)
|
|
697
96
|
|
|
698
|
-
|
|
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
|
-
```
|
|
97
|
+
## Part of the Arazzo Toolkit
|
|
715
98
|
|
|
716
|
-
|
|
99
|
+
`@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).
|