Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 65 additions & 45 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,11 +88,6 @@ test(expressions[0]); // => true
parse(expressions[0]); // => { result, tree }
```

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

#### Parsing

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

```abnf
; Arazzo runtime expression ABNF syntax
expression = ( "$url" / "$method" / "$statusCode" / "$request." source / "$response." source / "$inputs." name / "$outputs." name / "$steps." name / "$workflows." name / "$sourceDescriptions." name / "$components." name )
source = ( header-reference / query-reference / path-reference / body-reference )
header-reference = "header." token
query-reference = "query." name
path-reference = "path." name
body-reference = "body" ["#" json-pointer ]
name = *( CHAR )
expression = (
"$url" /
"$method" /
"$statusCode" /
"$request." source /
"$response." source /
"$inputs." inputs-reference /
"$outputs." outputs-reference /
"$steps." steps-reference /
"$workflows." workflows-reference /
"$sourceDescriptions." source-reference /
"$components." components-reference /
"$self"
)
; Request/Response sources
source = ( header-reference / query-reference / path-reference / body-reference )
header-reference = "header." token
query-reference = "query." name
path-reference = "path." name
body-reference = "body" ["#" json-pointer ]

; Input/Output references
inputs-reference = input-name ["#" json-pointer]
input-name = identifier
outputs-reference = output-name ["#" json-pointer]
output-name = identifier

; Steps expressions
steps-reference = step-id ".outputs." output-name ["#" json-pointer]
step-id = identifier-strict

; Workflows expressions
workflows-reference = workflow-id "." workflow-field "." workflow-field-name ["#" json-pointer]
workflow-id = identifier-strict
workflow-field = "inputs" / "outputs"
workflow-field-name = identifier

; Source descriptions expressions
source-reference = source-name "." source-reference-id
source-name = identifier-strict
source-reference-id = 1*CHAR

; Components expressions
components-reference = component-type "." component-name
component-type = "parameters" / "successActions" / "failureActions"
component-name = identifier

; Unconstrained name rule for query/path references
name = *( CHAR )

; Grammar for parsing template strings with embedded expressions
expression-string = *( literal-char / embedded-expression )
embedded-expression = "{" expression "}"
literal-char = %x00-7A / %x7C / %x7E-10FFFF ; anything except { (%x7B) and } (%x7D)

; Secondary grammar for parsing $steps name part
; Format: {stepId}.{field}.{subField}[#/{jsonPointer}]
steps-name = steps-id "." steps-field "." steps-sub-field ["#" json-pointer]
steps-id = 1*(ALPHA / DIGIT / "_" / "-")
steps-field = "outputs"
steps-sub-field = 1*(ALPHA / DIGIT / "." / "-" / "_")

; Secondary grammar for parsing $workflows name part
; Format: {workflowId}.{field}.{subField}[#/{jsonPointer}]
workflows-name = workflows-id "." workflows-field "." workflows-sub-field ["#" json-pointer]
workflows-id = 1*(ALPHA / DIGIT / "_" / "-")
workflows-field = "inputs" / "outputs"
workflows-sub-field = 1*(ALPHA / DIGIT / "." / "-" / "_")

; Secondary grammar for parsing $sourceDescriptions name part
; Format: {sourceName}.{reference}
; reference can be operationId (unconstrained) or workflowId (constrained)
source-descriptions-name = source-descriptions-source-name "." source-descriptions-reference
source-descriptions-source-name = 1*(ALPHA / DIGIT / "_" / "-")
source-descriptions-reference = 1*CHAR

; Secondary grammar for parsing $components name part
; Format: {field}.{subField}
; Allowed fields: parameters, successActions, failureActions
components-name = components-field "." components-sub-field
components-field = "parameters" / "successActions" / "failureActions"
components-sub-field = 1*(ALPHA / DIGIT / "." / "-" / "_")

; https://datatracker.ietf.org/doc/html/rfc6901#section-3
; JSON Pointer (RFC 6901, adapted)
; { (%x7B) and } (%x7D) are excluded from 'unescaped' for unambiguous embedded expression parsing
json-pointer = *( "/" reference-token )
reference-token = *( unescaped / escaped )
unescaped = %x00-2E / %x30-7D / %x7F-10FFFF
; %x2F ('/') and %x7E ('~') are excluded from 'unescaped'
unescaped = %x00-2E / %x30-7A / %x7C / %x7F-10FFFF
; %x2F ('/'), %x7E ('~'), %x7B ('{'), %x7D ('}') are excluded
escaped = "~" ( "0" / "1" )
; representing '~' and '/', respectively

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

; Identifier rules
identifier = 1*(ALPHA / DIGIT / "." / "-" / "_")
; Alphanumeric with dots, hyphens, underscores
identifier-strict = 1*(ALPHA / DIGIT / "_" / "-")
; Alphanumeric with hyphens, underscores (no dots)

; https://datatracker.ietf.org/doc/html/rfc5234#appendix-B.1
HEXDIG = DIGIT / "A" / "B" / "C" / "D" / "E" / "F"
DIGIT = %x30-39 ; 0-9
Expand All @@ -358,11 +374,15 @@ Request parameter | `$request.path.id` | Request parameters MUST be
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.
Request URL | `$url` |
Response value | `$response.body#/status` | In operations which return payloads, references may be made to portions of the response body or the entire body.
Response header | `$response.header.Server` | Single header values only are available
workflow input | `$inputs.username` or `$workflows.foo.inputs.username` | Single input values only are available
Response header | `$response.header.Server` | Single header values only are available.
Self URI | `$self` | References the canonical URI of the current Arazzo Description.
Workflow input | `$inputs.username` | Single input values only are available.
Workflow input property | `$inputs.customer#/firstName` | To access nested properties within an input object, use JSON Pointer syntax.
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.
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.
Source description reference | `$sourceDescriptions.petstore.getPetById` | References an operationId or workflowId from the named source description.
Components parameter | `$components.parameters.foo` | Accesses a foo parameter defined within the Components Object.
Components action | `$components.successActions.bar` | Accesses a success or failure action defined within the Components Object.

Runtime expressions preserve the type of the referenced value.
Expressions can be embedded into string values by surrounding the expression with `{}` curly braces.
Expand Down
97 changes: 59 additions & 38 deletions src/grammar.bnf
Original file line number Diff line number Diff line change
@@ -1,50 +1,65 @@
; Arazzo runtime expression ABNF syntax
expression = ( "$url" / "$method" / "$statusCode" / "$request." source / "$response." source / "$inputs." name / "$outputs." name / "$steps." name / "$workflows." name / "$sourceDescriptions." name / "$components." name )
source = ( header-reference / query-reference / path-reference / body-reference )
header-reference = "header." token
query-reference = "query." name
path-reference = "path." name
body-reference = "body" ["#" json-pointer ]
name = *( CHAR )
expression = (
"$url" /
"$method" /
"$statusCode" /
"$request." source /
"$response." source /
"$inputs." inputs-reference /
"$outputs." outputs-reference /
"$steps." steps-reference /
"$workflows." workflows-reference /
"$sourceDescriptions." source-reference /
"$components." components-reference /
"$self"
)
; Request/Response sources
source = ( header-reference / query-reference / path-reference / body-reference )
header-reference = "header." token
query-reference = "query." name
path-reference = "path." name
body-reference = "body" ["#" json-pointer ]

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

; Steps expressions
steps-reference = step-id ".outputs." output-name ["#" json-pointer]
step-id = identifier-strict

; Secondary grammar for parsing $steps name part
; Format: {stepId}.{field}.{subField}[#/{jsonPointer}]
steps-name = steps-id "." steps-field "." steps-sub-field ["#" json-pointer]
steps-id = 1*(ALPHA / DIGIT / "_" / "-")
steps-field = "outputs"
steps-sub-field = 1*(ALPHA / DIGIT / "." / "-" / "_")
; Workflows expressions
workflows-reference = workflow-id "." workflow-field "." workflow-field-name ["#" json-pointer]
workflow-id = identifier-strict
workflow-field = "inputs" / "outputs"
workflow-field-name = identifier

; Secondary grammar for parsing $workflows name part
; Format: {workflowId}.{field}.{subField}[#/{jsonPointer}]
workflows-name = workflows-id "." workflows-field "." workflows-sub-field ["#" json-pointer]
workflows-id = 1*(ALPHA / DIGIT / "_" / "-")
workflows-field = "inputs" / "outputs"
workflows-sub-field = 1*(ALPHA / DIGIT / "." / "-" / "_")
; Source descriptions expressions
source-reference = source-name "." source-reference-id
source-name = identifier-strict
source-reference-id = 1*CHAR

; Secondary grammar for parsing $sourceDescriptions name part
; Format: {sourceName}.{reference}
; reference can be operationId (unconstrained) or workflowId (constrained)
source-descriptions-name = source-descriptions-source-name "." source-descriptions-reference
source-descriptions-source-name = 1*(ALPHA / DIGIT / "_" / "-")
source-descriptions-reference = 1*CHAR
; Components expressions
components-reference = component-type "." component-name
component-type = "parameters" / "successActions" / "failureActions"
component-name = identifier

; Secondary grammar for parsing $components name part
; Format: {field}.{subField}
; Allowed fields: parameters, successActions, failureActions
components-name = components-field "." components-sub-field
components-field = "parameters" / "successActions" / "failureActions"
components-sub-field = 1*(ALPHA / DIGIT / "." / "-" / "_")
; Unconstrained name rule for query/path references
name = *( CHAR )

; https://datatracker.ietf.org/doc/html/rfc6901#section-3
; Grammar for parsing template strings with embedded expressions
expression-string = *( literal-char / embedded-expression )
embedded-expression = "{" expression "}"
literal-char = %x00-7A / %x7C / %x7E-10FFFF ; anything except { (%x7B) and } (%x7D)

; JSON Pointer (RFC 6901, adapted)
; { (%x7B) and } (%x7D) are excluded from 'unescaped' for unambiguous embedded expression parsing
json-pointer = *( "/" reference-token )
reference-token = *( unescaped / escaped )
unescaped = %x00-2E / %x30-7D / %x7F-10FFFF
; %x2F ('/') and %x7E ('~') are excluded from 'unescaped'
unescaped = %x00-2E / %x30-7A / %x7C / %x7F-10FFFF
; %x2F ('/'), %x7E ('~'), %x7B ('{'), %x7D ('}') are excluded
escaped = "~" ( "0" / "1" )
; representing '~' and '/', respectively

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

; Identifier rules
identifier = 1*(ALPHA / DIGIT / "." / "-" / "_")
; Alphanumeric with dots, hyphens, underscores
identifier-strict = 1*(ALPHA / DIGIT / "_" / "-")
; Alphanumeric with hyphens, underscores (no dots)

; https://datatracker.ietf.org/doc/html/rfc5234#appendix-B.1
HEXDIG = DIGIT / "A" / "B" / "C" / "D" / "E" / "F"
DIGIT = %x30-39 ; 0-9
Expand Down
Loading
Loading