Skip to content

Commit 1b1caaf

Browse files
char0nclaude
andcommitted
feat: improve ABNF grammar clarity and inline all secondary grammars
Restructure the ABNF grammar to use explicit, typed reference rules in the primary grammar instead of relying on secondary grammars with two-pass parsing. This improves grammar clarity and aligns with the proposed spec changes in OAI/Arazzo-Specification#454. Key changes: - Add $self expression support - Add $inputs/$outputs JSON Pointer support (e.g., $inputs.customer#/firstName) - Inline all secondary grammars into the primary grammar - Extract shared identifier and identifier-strict rules - Adapt json-pointer to exclude { and } from unescaped for unambiguous embedded expression parsing, fixing the body expression extract limitation - Require explicit component types (parameters/successActions/failureActions) - Update README with current grammar and examples Resolves: OAI/Arazzo-Specification#424, OAI/Arazzo-Specification#425, OAI/Arazzo-Specification#426, OAI/Arazzo-Specification#428 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 9711f86 commit 1b1caaf

12 files changed

Lines changed: 1643 additions & 578 deletions

File tree

README.md

Lines changed: 64 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -88,11 +88,6 @@ test(expressions[0]); // => true
8888
parse(expressions[0]); // => { result, tree }
8989
```
9090

91-
**Known limitation:** `$request.body#/...` and `$response.body#/...` expressions with JSON pointers cannot be reliably
92-
extracted from `{expression}` syntax. This is because [RFC 6901](https://datatracker.ietf.org/doc/html/rfc6901)
93-
(JSON Pointer) allows the `}` character in pointer paths, making it impossible to determine where the expression ends.
94-
Use `parse()` directly on the raw expression for these cases.
95-
9691
#### Parsing
9792

9893
Parsing a Runtime Expression is as simple as importing the **parse** function and calling it.
@@ -266,52 +261,66 @@ The runtime expression is defined by the following [ABNF](https://tools.ietf.org
266261

267262
```abnf
268263
; Arazzo runtime expression ABNF syntax
269-
expression = ( "$url" / "$method" / "$statusCode" / "$request." source / "$response." source / "$inputs." name / "$outputs." name / "$steps." name / "$workflows." name / "$sourceDescriptions." name / "$components." name )
270-
source = ( header-reference / query-reference / path-reference / body-reference )
271-
header-reference = "header." token
272-
query-reference = "query." name
273-
path-reference = "path." name
274-
body-reference = "body" ["#" json-pointer ]
275-
name = *( CHAR )
264+
expression = (
265+
"$url" /
266+
"$method" /
267+
"$statusCode" /
268+
"$request." source /
269+
"$response." source /
270+
"$inputs." inputs-reference /
271+
"$outputs." outputs-reference /
272+
"$steps." steps-reference /
273+
"$workflows." workflows-reference /
274+
"$sourceDescriptions." source-reference /
275+
"$components." components-reference /
276+
"$self"
277+
)
278+
; Request/Response sources
279+
source = ( header-reference / query-reference / path-reference / body-reference )
280+
header-reference = "header." token
281+
query-reference = "query." name
282+
path-reference = "path." name
283+
body-reference = "body" ["#" json-pointer ]
284+
285+
; Input/Output references
286+
inputs-reference = inputs-name ["#" json-pointer]
287+
inputs-name = identifier
288+
outputs-reference = outputs-name ["#" json-pointer]
289+
outputs-name = identifier
290+
291+
; Steps expressions
292+
steps-reference = steps-id ".outputs." outputs-name ["#" json-pointer]
293+
steps-id = identifier-strict
294+
295+
; Workflows expressions
296+
workflows-reference = workflows-id "." workflows-field "." workflows-field-name ["#" json-pointer]
297+
workflows-id = identifier-strict
298+
workflows-field = "inputs" / "outputs"
299+
workflows-field-name = identifier
300+
301+
; Source descriptions expressions
302+
source-reference = source-descriptions-name "." source-descriptions-reference
303+
source-descriptions-name = identifier-strict
304+
source-descriptions-reference = 1*CHAR
305+
306+
; Components expressions
307+
components-reference = components-type "." components-name
308+
components-type = "parameters" / "successActions" / "failureActions"
309+
components-name = identifier
310+
311+
name = *( CHAR )
276312
277313
; Grammar for parsing template strings with embedded expressions
278314
expression-string = *( literal-char / embedded-expression )
279315
embedded-expression = "{" expression "}"
280316
literal-char = %x00-7A / %x7C / %x7E-10FFFF ; anything except { (%x7B) and } (%x7D)
281317
282-
; Secondary grammar for parsing $steps name part
283-
; Format: {stepId}.{field}.{subField}[#/{jsonPointer}]
284-
steps-name = steps-id "." steps-field "." steps-sub-field ["#" json-pointer]
285-
steps-id = 1*(ALPHA / DIGIT / "_" / "-")
286-
steps-field = "outputs"
287-
steps-sub-field = 1*(ALPHA / DIGIT / "." / "-" / "_")
288-
289-
; Secondary grammar for parsing $workflows name part
290-
; Format: {workflowId}.{field}.{subField}[#/{jsonPointer}]
291-
workflows-name = workflows-id "." workflows-field "." workflows-sub-field ["#" json-pointer]
292-
workflows-id = 1*(ALPHA / DIGIT / "_" / "-")
293-
workflows-field = "inputs" / "outputs"
294-
workflows-sub-field = 1*(ALPHA / DIGIT / "." / "-" / "_")
295-
296-
; Secondary grammar for parsing $sourceDescriptions name part
297-
; Format: {sourceName}.{reference}
298-
; reference can be operationId (unconstrained) or workflowId (constrained)
299-
source-descriptions-name = source-descriptions-source-name "." source-descriptions-reference
300-
source-descriptions-source-name = 1*(ALPHA / DIGIT / "_" / "-")
301-
source-descriptions-reference = 1*CHAR
302-
303-
; Secondary grammar for parsing $components name part
304-
; Format: {field}.{subField}
305-
; Allowed fields: parameters, successActions, failureActions
306-
components-name = components-field "." components-sub-field
307-
components-field = "parameters" / "successActions" / "failureActions"
308-
components-sub-field = 1*(ALPHA / DIGIT / "." / "-" / "_")
309-
310-
; https://datatracker.ietf.org/doc/html/rfc6901#section-3
318+
; JSON Pointer (RFC 6901, adapted)
319+
; { (%x7B) and } (%x7D) are excluded from 'unescaped' for unambiguous embedded expression parsing
311320
json-pointer = *( "/" reference-token )
312321
reference-token = *( unescaped / escaped )
313-
unescaped = %x00-2E / %x30-7D / %x7F-10FFFF
314-
; %x2F ('/') and %x7E ('~') are excluded from 'unescaped'
322+
unescaped = %x00-2E / %x30-7A / %x7C / %x7E-10FFFF
323+
; %x2F ('/'), %x7E ('~'), %x7B ('{'), %x7D ('}') are excluded
315324
escaped = "~" ( "0" / "1" )
316325
; representing '~' and '/', respectively
317326
@@ -338,6 +347,12 @@ escape = %x5C ; \
338347
unescape = %x20-21 / %x23-5B / %x5D-7A / %x7C / %x7E-10FFFF
339348
; %x7B ('{') and %x7D ('}') are excluded from 'unescape'
340349
350+
; Identifier rules
351+
identifier = 1*(ALPHA / DIGIT / "." / "-" / "_")
352+
; Alphanumeric with dots, hyphens, underscores
353+
identifier-strict = 1*(ALPHA / DIGIT / "_" / "-")
354+
; Alphanumeric with hyphens, underscores (no dots)
355+
341356
; https://datatracker.ietf.org/doc/html/rfc5234#appendix-B.1
342357
HEXDIG = DIGIT / "A" / "B" / "C" / "D" / "E" / "F"
343358
DIGIT = %x30-39 ; 0-9
@@ -358,11 +373,15 @@ Request parameter | `$request.path.id` | Request parameters MUST be
358373
Request body property | `$request.body#/user/uuid` | In operations which accept payloads, references may be made to portions of the `requestBody` or the entire body.
359374
Request URL | `$url` |
360375
Response value | `$response.body#/status` | In operations which return payloads, references may be made to portions of the response body or the entire body.
361-
Response header | `$response.header.Server` | Single header values only are available
362-
workflow input | `$inputs.username` or `$workflows.foo.inputs.username` | Single input values only are available
376+
Response header | `$response.header.Server` | Single header values only are available.
377+
Self URI | `$self` | References the canonical URI of the current Arazzo Description.
378+
Workflow input | `$inputs.username` | Single input values only are available.
379+
Workflow input property | `$inputs.customer#/firstName` | To access nested properties within an input object, use JSON Pointer syntax.
363380
Step output value | `$steps.someStepId.outputs.pets` | In situations where the output named property return payloads, references may be made to portions of the response body (e.g., `$steps.someStepId.outputs.pets#/0/id`) or the entire body.
364381
Workflow output value | `$outputs.bar` or `$workflows.foo.outputs.bar` | In situations where the output named property return payloads, references may be made to portions of the response body (e.g., `$workflows.foo.outputs.mappedResponse#/name`) or the entire body.
382+
Source description reference | `$sourceDescriptions.petstore.getPetById` | References an operationId or workflowId from the named source description.
365383
Components parameter | `$components.parameters.foo` | Accesses a foo parameter defined within the Components Object.
384+
Components action | `$components.successActions.bar` | Accesses a success or failure action defined within the Components Object.
366385

367386
Runtime expressions preserve the type of the referenced value.
368387
Expressions can be embedded into string values by surrounding the expression with `{}` curly braces.

src/grammar.bnf

Lines changed: 57 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,64 @@
11
; Arazzo runtime expression ABNF syntax
2-
expression = ( "$url" / "$method" / "$statusCode" / "$request." source / "$response." source / "$inputs." name / "$outputs." name / "$steps." name / "$workflows." name / "$sourceDescriptions." name / "$components." name )
3-
source = ( header-reference / query-reference / path-reference / body-reference )
4-
header-reference = "header." token
5-
query-reference = "query." name
6-
path-reference = "path." name
7-
body-reference = "body" ["#" json-pointer ]
8-
name = *( CHAR )
2+
expression = (
3+
"$url" /
4+
"$method" /
5+
"$statusCode" /
6+
"$request." source /
7+
"$response." source /
8+
"$inputs." inputs-reference /
9+
"$outputs." outputs-reference /
10+
"$steps." steps-reference /
11+
"$workflows." workflows-reference /
12+
"$sourceDescriptions." source-reference /
13+
"$components." components-reference /
14+
"$self"
15+
)
16+
; Request/Response sources
17+
source = ( header-reference / query-reference / path-reference / body-reference )
18+
header-reference = "header." token
19+
query-reference = "query." name
20+
path-reference = "path." name
21+
body-reference = "body" ["#" json-pointer ]
922

10-
; Grammar for parsing template strings with embedded expressions
11-
expression-string = *( literal-char / embedded-expression )
12-
embedded-expression = "{" expression "}"
13-
literal-char = %x00-7A / %x7C / %x7E-10FFFF ; anything except { (%x7B) and } (%x7D)
23+
; Input/Output references
24+
inputs-reference = inputs-name ["#" json-pointer]
25+
inputs-name = identifier
26+
outputs-reference = outputs-name ["#" json-pointer]
27+
outputs-name = identifier
1428

15-
; Secondary grammar for parsing $steps name part
16-
; Format: {stepId}.{field}.{subField}[#/{jsonPointer}]
17-
steps-name = steps-id "." steps-field "." steps-sub-field ["#" json-pointer]
18-
steps-id = 1*(ALPHA / DIGIT / "_" / "-")
19-
steps-field = "outputs"
20-
steps-sub-field = 1*(ALPHA / DIGIT / "." / "-" / "_")
29+
; Steps expressions
30+
steps-reference = steps-id ".outputs." outputs-name ["#" json-pointer]
31+
steps-id = identifier-strict
2132

22-
; Secondary grammar for parsing $workflows name part
23-
; Format: {workflowId}.{field}.{subField}[#/{jsonPointer}]
24-
workflows-name = workflows-id "." workflows-field "." workflows-sub-field ["#" json-pointer]
25-
workflows-id = 1*(ALPHA / DIGIT / "_" / "-")
26-
workflows-field = "inputs" / "outputs"
27-
workflows-sub-field = 1*(ALPHA / DIGIT / "." / "-" / "_")
33+
; Workflows expressions
34+
workflows-reference = workflows-id "." workflows-field "." workflows-field-name ["#" json-pointer]
35+
workflows-id = identifier-strict
36+
workflows-field = "inputs" / "outputs"
37+
workflows-field-name = identifier
2838

29-
; Secondary grammar for parsing $sourceDescriptions name part
30-
; Format: {sourceName}.{reference}
31-
; reference can be operationId (unconstrained) or workflowId (constrained)
32-
source-descriptions-name = source-descriptions-source-name "." source-descriptions-reference
33-
source-descriptions-source-name = 1*(ALPHA / DIGIT / "_" / "-")
39+
; Source descriptions expressions
40+
source-reference = source-descriptions-name "." source-descriptions-reference
41+
source-descriptions-name = identifier-strict
3442
source-descriptions-reference = 1*CHAR
3543

36-
; Secondary grammar for parsing $components name part
37-
; Format: {field}.{subField}
38-
; Allowed fields: parameters, successActions, failureActions
39-
components-name = components-field "." components-sub-field
40-
components-field = "parameters" / "successActions" / "failureActions"
41-
components-sub-field = 1*(ALPHA / DIGIT / "." / "-" / "_")
44+
; Components expressions
45+
components-reference = components-type "." components-name
46+
components-type = "parameters" / "successActions" / "failureActions"
47+
components-name = identifier
48+
49+
name = *( CHAR )
4250

43-
; https://datatracker.ietf.org/doc/html/rfc6901#section-3
51+
; Grammar for parsing template strings with embedded expressions
52+
expression-string = *( literal-char / embedded-expression )
53+
embedded-expression = "{" expression "}"
54+
literal-char = %x00-7A / %x7C / %x7E-10FFFF ; anything except { (%x7B) and } (%x7D)
55+
56+
; JSON Pointer (RFC 6901, adapted)
57+
; { (%x7B) and } (%x7D) are excluded from 'unescaped' for unambiguous embedded expression parsing
4458
json-pointer = *( "/" reference-token )
4559
reference-token = *( unescaped / escaped )
46-
unescaped = %x00-2E / %x30-7D / %x7F-10FFFF
47-
; %x2F ('/') and %x7E ('~') are excluded from 'unescaped'
60+
unescaped = %x00-2E / %x30-7A / %x7C / %x7E-10FFFF
61+
; %x2F ('/'), %x7E ('~'), %x7B ('{'), %x7D ('}') are excluded
4862
escaped = "~" ( "0" / "1" )
4963
; representing '~' and '/', respectively
5064

@@ -71,6 +85,12 @@ escape = %x5C ; \
7185
unescape = %x20-21 / %x23-5B / %x5D-7A / %x7C / %x7E-10FFFF
7286
; %x7B ('{') and %x7D ('}') are excluded from 'unescape'
7387

88+
; Identifier rules
89+
identifier = 1*(ALPHA / DIGIT / "." / "-" / "_")
90+
; Alphanumeric with dots, hyphens, underscores
91+
identifier-strict = 1*(ALPHA / DIGIT / "_" / "-")
92+
; Alphanumeric with hyphens, underscores (no dots)
93+
7494
; https://datatracker.ietf.org/doc/html/rfc5234#appendix-B.1
7595
HEXDIG = DIGIT / "A" / "B" / "C" / "D" / "E" / "F"
7696
DIGIT = %x30-39 ; 0-9

0 commit comments

Comments
 (0)