From 64e553f8cb2e7bd4c733f679881d2d8a9bfedd19 Mon Sep 17 00:00:00 2001 From: Frank Kilcommins Date: Mon, 23 Mar 2026 11:18:11 +0000 Subject: [PATCH 1/5] feat(runtime-expressions): improve ABNF grammar clarity --- examples/1.0.0/bnpl-arazzo.yaml | 10 +-- src/arazzo.md | 147 +++++++++++++++++++++++++++----- 2 files changed, 131 insertions(+), 26 deletions(-) diff --git a/examples/1.0.0/bnpl-arazzo.yaml b/examples/1.0.0/bnpl-arazzo.yaml index e8034ad..b0292a8 100644 --- a/examples/1.0.0/bnpl-arazzo.yaml +++ b/examples/1.0.0/bnpl-arazzo.yaml @@ -167,10 +167,10 @@ workflows: contentType: application/json payload: | { - "firstName": "{$inputs.customer.firstName}", - "lastName": "{$inputs.customer.lastName}", - "dateOfBirth": "{$inputs.customer.dateOfBirth}", - "postalCode": "{$inputs.customer.postalCode}" + "firstName": "{$inputs.customer#/firstName}", + "lastName": "{$inputs.customer#/lastName}", + "dateOfBirth": "{$inputs.customer#/dateOfBirth}", + "postalCode": "{$inputs.customer#/postalCode}" "termsAndConditionsAccepted": true } successCriteria: @@ -194,7 +194,7 @@ workflows: contentType: application/json payload: | { - "enrolledCustomer": "{$inputs.customer.uri}", + "enrolledCustomer": "{$inputs.customer#/uri}", "newCustomer": "{$steps.createCustomer.outputs.customer}", "products": "{$steps.checkLoanCanBeProvided.outputs.eligibleProducts}" } diff --git a/src/arazzo.md b/src/arazzo.md index 248b017..020f842 100644 --- a/src/arazzo.md +++ b/src/arazzo.md @@ -172,7 +172,7 @@ workflows: value: 'available' - name: Authorization in: header - value: $steps.loginUser.outputs.sessionToken + value: $steps.loginStep.outputs.sessionToken successCriteria: - condition: $statusCode == 200 outputs: @@ -584,7 +584,7 @@ components: "workflowId": "refreshTokenWorkflowId", "criteria": [ { - "condition": "{$statusCode == 401}" + "condition": "$statusCode == 401" } ] } @@ -939,23 +939,91 @@ A runtime expression allows values to be defined based on information that will The runtime expression is defined by the following [ABNF](https://tools.ietf.org/html/rfc5234) syntax: ```abnf - expression = ( "$url" / "$method" / "$statusCode" / "$request." source / "$response." source / "$inputs." name / "$outputs." name / "$steps." name / "$workflows." name / "$sourceDescriptions." name / "$components." name / "$components.parameters." parameter-name) - parameter-name = name ; Reuses 'name' rule for parameter names - 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 ] - json-pointer = *( "/" reference-token ) - reference-token = *( unescaped / escaped ) - unescaped = %x00-2E / %x30-7D / %x7F-10FFFF - ; %x2F ('/') and %x7E ('~') are excluded from 'unescaped' - escaped = "~" ( "0" / "1" ) - ; representing '~' and '/', respectively - name = *( CHAR ) - token = 1*tchar - tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*" / "+" / "-" / "." / - "^" / "_" / "`" / "|" / "~" / DIGIT / ALPHA + ; Top-level expression + expression = ( + "$url" / + "$method" / + "$statusCode" / + "$request." source / + "$response." source / + "$inputs." input-reference / + "$outputs." output-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 + input-reference = input-name [ "#" json-pointer ] + output-reference = output-name [ "#" json-pointer ] + input-name = identifier + output-name = identifier + + ; Steps expressions + steps-reference = step-id ".outputs." output-name [ "#" json-pointer ] + step-id = identifier + + ; Workflows expressions + workflows-reference = workflow-id "." workflow-field "." field-name [ "#" json-pointer ] + workflow-id = identifier + workflow-field = "inputs" / "outputs" + field-name = identifier + + ; Source descriptions expressions + source-reference = source-name "." reference-id + source-name = identifier + reference-id = identifier + ; Resolution priority defined in spec text: (1) operationId/workflowId, (2) field names + + ; Components expressions + components-reference = component-type "." component-name + component-type = "parameters" / "successActions" / "failureActions" + component-name = identifier + + ; Identifier rule + identifier = 1*( ALPHA / DIGIT / "." / "-" / "_" ) + ; Alphanumeric with dots, hyphens, underscores + ; Matches recommended pattern [A-Za-z0-9_\-]+ from spec + + ; Legacy 'name' rule (retained for query/path references) + name = *( CHAR ) + + ; JSON Pointer (RFC 6901) + json-pointer = *( "/" reference-token ) + reference-token = *( unescaped / escaped ) + unescaped = %x00-2E / %x30-7D / %x7F-10FFFF + ; %x2F ('/') and %x7E ('~') excluded from 'unescaped' + escaped = "~" ( "0" / "1" ) + ; representing '~' and '/', respectively + + ; Token for header names (RFC 9110) + token = 1*tchar + tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*" / "+" / "-" / "." / + "^" / "_" / "`" / "|" / "~" / DIGIT / ALPHA + + ; CHAR definition + CHAR = %x00-7A / %x7C / %x7E-10FFFF + ; Excludes left brace { (%x7B) and right brace } (%x7D) + ; for unambiguous embedded expression parsing + + ; Expression strings + expression-string = *( literal-char / embedded-expression ) + embedded-expression = "{" expression "}" + literal-char = %x00-7A / %x7C / %x7E-10FFFF + ; Same as CHAR - excludes { and } + + ; Core ABNF rules (RFC 5234) + ALPHA = %x41-5A / %x61-7A ; A-Z / a-z + DIGIT = %x30-39 ; 0-9 ``` #### Examples @@ -969,14 +1037,51 @@ The runtime expression is defined by the following [ABNF](https://tools.ietf.org | 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. | +| Self URI | `$self` | **NEW in 1.1.0.** References the canonical URI of the current Arazzo Description as defined by the `$self` field. | +| 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. The input name is `customer`, and `#/firstName` is the JSON Pointer to the nested property. | | 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. Resolution priority: (1) operationId/workflowId, (2) field names. | | Components parameter | `$components.parameters.foo` | Accesses a foo parameter defined within the Components Object. | +| Components action | `$components.successActions.bar` or `$components.failureActions.baz` | 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. +Expressions can be embedded into string values by surrounding the expression with `{}` curly braces. When a runtime expression is embedded in this manner, the following rules apply based on the value type: + +- Scalar values (string, number, boolean, null) are converted to their string representation. +- Complex values (object, array) are handled based on their current representation: + - If the value is already a string (e.g., an XML or YAML response body stored without parsing), it is embedded as-is without modification. + - If the value is a parsed structure (e.g., a JSON object or array from a parsed response, or workflow input), it MUST be serialized as JSON per RFC 8259. + +Whether a value is stored as a string or parsed structure depends on its content type. JSON responses and inputs are typically parsed into structures, while XML and plain text are typically stored as strings. When embedding a parsed structure into a non-JSON payload format, the resulting JSON serialization may not match the target format's expected structure. + +#### Source Description Expression Resolution + +When using `$sourceDescriptions..`, the `` portion is resolved with the following priority: + +- **operationId or workflowId** - If the referenced source description is an OpenAPI description, `` is first matched against operationIds. If the source description is an Arazzo document, `` is matched against workflowIds. +- **Source description field** - If no operationId/workflowId match is found, `` is matched against field names of the Source Description Object (e.g., `url`, +`type`). + +**Examples:** + +Given this source description: + +```yaml +sourceDescriptions: + - name: petstore + url: https://api.example.com/petstore.yaml + type: openapi +``` + +Given the above example source description and an OpenAPI description at that specified URL containing an operation with operationId: `getPetById`: + +- `$sourceDescriptions.petstore.getPetById` resolves to the operation with operationId `getPetById` (_priority 1_) +- `$sourceDescriptions.petstore.url` resolves to `https://api.example.com/petstore.yaml` (_priority 2_) +- `$sourceDescriptions.petstore.type` resolves to `openapi` (_priority 2_) +If an operationId happens to conflict with a field name (e.g., an operation with operationId: url), the operationId takes precedence. ### Specification Extensions From c7eb3acbd2b1098b553585aad4f615a3946010ad Mon Sep 17 00:00:00 2001 From: Frank Kilcommins Date: Mon, 6 Apr 2026 15:59:14 +0100 Subject: [PATCH 2/5] chore(spec): address PR comments on updated ABNF grammar --- src/arazzo.md | 45 +++++++++++++++++++++++++++------------------ 1 file changed, 27 insertions(+), 18 deletions(-) diff --git a/src/arazzo.md b/src/arazzo.md index 020f842..5ce1e2a 100644 --- a/src/arazzo.md +++ b/src/arazzo.md @@ -946,8 +946,8 @@ The runtime expression is defined by the following [ABNF](https://tools.ietf.org "$statusCode" / "$request." source / "$response." source / - "$inputs." input-reference / - "$outputs." output-reference / + "$inputs." inputs-reference / + "$outputs." outputs-reference / "$steps." steps-reference / "$workflows." workflows-reference / "$sourceDescriptions." source-reference / @@ -963,25 +963,26 @@ The runtime expression is defined by the following [ABNF](https://tools.ietf.org body-reference = "body" ["#" json-pointer ] ; Input/Output references - input-reference = input-name [ "#" json-pointer ] - output-reference = output-name [ "#" json-pointer ] - input-name = identifier - output-name = identifier + inputs-reference = input-name [ "#" json-pointer ] + outputs-reference = output-name [ "#" json-pointer ] + input-name = name + output-name = name ; Steps expressions steps-reference = step-id ".outputs." output-name [ "#" json-pointer ] - step-id = identifier + step-id = identifier-strict ; Workflows expressions workflows-reference = workflow-id "." workflow-field "." field-name [ "#" json-pointer ] - workflow-id = identifier + workflow-id = identifier-strict workflow-field = "inputs" / "outputs" - field-name = identifier + field-name = name ; Source descriptions expressions source-reference = source-name "." reference-id - source-name = identifier - reference-id = identifier + source-name = identifier-strict + reference-id = 1*CHAR + ; operationIds have no character restrictions in OpenAPI/AsyncAPI ; Resolution priority defined in spec text: (1) operationId/workflowId, (2) field names ; Components expressions @@ -989,19 +990,23 @@ The runtime expression is defined by the following [ABNF](https://tools.ietf.org component-type = "parameters" / "successActions" / "failureActions" component-name = identifier - ; Identifier rule + ; Identifier rules + identifier-strict = 1*( ALPHA / DIGIT / "-" / "_" ) + ; For step IDs, workflow IDs, and sourceDescription names (no dots) + ; Matches [A-Za-z0-9_\-]+ + identifier = 1*( ALPHA / DIGIT / "." / "-" / "_" ) - ; Alphanumeric with dots, hyphens, underscores - ; Matches recommended pattern [A-Za-z0-9_\-]+ from spec + ; For component keys (dots allowed) + ; Matches [a-zA-Z0-9\.\-_]+ - ; Legacy 'name' rule (retained for query/path references) name = *( CHAR ) + ; Allows unrestricted characters for query/path parameter names and field references ; JSON Pointer (RFC 6901) json-pointer = *( "/" reference-token ) reference-token = *( unescaped / escaped ) - unescaped = %x00-2E / %x30-7D / %x7F-10FFFF - ; %x2F ('/') and %x7E ('~') excluded from 'unescaped' + unescaped = %x00-2E / %x30-7A / %x7C / %x7E-10FFFF + ; Excludes / (%x2F), { (%x7B), } (%x7D), and ~ (%x7E) escaped = "~" ( "0" / "1" ) ; representing '~' and '/', respectively @@ -1036,13 +1041,17 @@ The runtime expression is defined by the following [ABNF](https://tools.ietf.org | 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 array element | `$response.body#/items/0/id` | Array elements can be accessed using numeric indices in JSON Pointer syntax. | | Response header | `$response.header.Server` | Single header values only are available. | -| Self URI | `$self` | **NEW in 1.1.0.** References the canonical URI of the current Arazzo Description as defined by the `$self` field. | +| Self URI | `$self` | References the canonical URI of the current Arazzo Description as defined by the `$self` field. | | 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. The input name is `customer`, and `#/firstName` is the JSON Pointer to the nested property. | | 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. | +| Step output deep nested | `$steps.fetchUser.outputs.data#/profile/address/postalCode` | JSON Pointers can traverse multiple levels to access deeply nested properties. | | 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. | +| Embedded expressions | `https://{$inputs.host}/api/{$steps.create.outputs.id}/status` | Multiple runtime expressions can be embedded within a single string value by wrapping each in curly braces. | | Source description reference | `$sourceDescriptions.petstore.getPetById` | References an operationId or workflowId from the named source description. Resolution priority: (1) operationId/workflowId, (2) field names. | +| Source description field | `$sourceDescriptions.petstore.url` | References a field from the Source Description Object. Resolved when no matching operationId/workflowId is found. | | Components parameter | `$components.parameters.foo` | Accesses a foo parameter defined within the Components Object. | | Components action | `$components.successActions.bar` or `$components.failureActions.baz` | Accesses a success or failure action defined within the Components Object. | From 2f2b50e144ff0dac33b14b24a3848446d62431b0 Mon Sep 17 00:00:00 2001 From: Frank Kilcommins Date: Mon, 6 Apr 2026 16:40:44 +0100 Subject: [PATCH 3/5] chore(spec): fix unescaped tilde exclusion. --- examples/1.0.0/bnpl-arazzo.yaml | 2 +- src/arazzo.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/1.0.0/bnpl-arazzo.yaml b/examples/1.0.0/bnpl-arazzo.yaml index b0292a8..a3a88a0 100644 --- a/examples/1.0.0/bnpl-arazzo.yaml +++ b/examples/1.0.0/bnpl-arazzo.yaml @@ -170,7 +170,7 @@ workflows: "firstName": "{$inputs.customer#/firstName}", "lastName": "{$inputs.customer#/lastName}", "dateOfBirth": "{$inputs.customer#/dateOfBirth}", - "postalCode": "{$inputs.customer#/postalCode}" + "postalCode": "{$inputs.customer#/postalCode}", "termsAndConditionsAccepted": true } successCriteria: diff --git a/src/arazzo.md b/src/arazzo.md index 5ce1e2a..3d90805 100644 --- a/src/arazzo.md +++ b/src/arazzo.md @@ -1005,7 +1005,7 @@ The runtime expression is defined by the following [ABNF](https://tools.ietf.org ; JSON Pointer (RFC 6901) json-pointer = *( "/" reference-token ) reference-token = *( unescaped / escaped ) - unescaped = %x00-2E / %x30-7A / %x7C / %x7E-10FFFF + unescaped = %x00-2E / %x30-7A / %x7C / %x7F-10FFFF ; Excludes / (%x2F), { (%x7B), } (%x7D), and ~ (%x7E) escaped = "~" ( "0" / "1" ) ; representing '~' and '/', respectively From 7dfadbfb1916fbdc191d60dc9fffa8638d943527 Mon Sep 17 00:00:00 2001 From: Frank Kilcommins Date: Wed, 8 Apr 2026 17:39:51 +0100 Subject: [PATCH 4/5] chore(spec): address feedback and leverage implementation example from @char0n --- src/arazzo.md | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/src/arazzo.md b/src/arazzo.md index 3d90805..5cabb92 100644 --- a/src/arazzo.md +++ b/src/arazzo.md @@ -965,18 +965,18 @@ The runtime expression is defined by the following [ABNF](https://tools.ietf.org ; Input/Output references inputs-reference = input-name [ "#" json-pointer ] outputs-reference = output-name [ "#" json-pointer ] - input-name = name - output-name = name + input-name = identifier + 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 "." field-name [ "#" json-pointer ] + workflows-reference = workflow-id "." workflow-field "." workflow-field-name [ "#" json-pointer ] workflow-id = identifier-strict workflow-field = "inputs" / "outputs" - field-name = name + workflow-field-name = identifier ; Source descriptions expressions source-reference = source-name "." reference-id @@ -1015,20 +1015,31 @@ The runtime expression is defined by the following [ABNF](https://tools.ietf.org tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*" / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~" / DIGIT / ALPHA - ; CHAR definition - CHAR = %x00-7A / %x7C / %x7E-10FFFF - ; Excludes left brace { (%x7B) and right brace } (%x7D) - ; for unambiguous embedded expression parsing + ; CHAR definition (RFC 7159, adapted to exclude { and }) + CHAR = unescape / escape ( + %x22 / ; " quotation mark U+0022 + %x5C / ; \ reverse solidus U+005C + %x2F / ; / solidus U+002F + %x62 / ; b backspace U+0008 + %x66 / ; f form feed U+000C + %x6E / ; n line feed U+000A + %x72 / ; r carriage return U+000D + %x74 / ; t tab U+0009 + %x75 4HEXDIG ) ; uXXXX U+XXXX + escape = %x5C ; \ + unescape = %x20-21 / %x23-5B / %x5D-7A / %x7C / %x7E-10FFFF + ; Excludes { (%x7B) and } (%x7D) for unambiguous embedded expression parsing ; Expression strings expression-string = *( literal-char / embedded-expression ) embedded-expression = "{" expression "}" literal-char = %x00-7A / %x7C / %x7E-10FFFF - ; Same as CHAR - excludes { and } + ; Excludes { and } - simpler than CHAR for literal text ; Core ABNF rules (RFC 5234) ALPHA = %x41-5A / %x61-7A ; A-Z / a-z - DIGIT = %x30-39 ; 0-9 + DIGIT = %x30-39 ; 0-9 + HEXDIG = DIGIT / "A" / "B" / "C" / "D" / "E" / "F" ``` #### Examples From a3b559c5f3436f0036ba5f0865ccbacbd3a4375a Mon Sep 17 00:00:00 2001 From: Frank Kilcommins Date: Thu, 9 Apr 2026 17:58:24 +0100 Subject: [PATCH 5/5] chore(spec): rename `reference-id` to `source-reference-id` to improve naming clarity --- src/arazzo.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/arazzo.md b/src/arazzo.md index 5cabb92..2f3a186 100644 --- a/src/arazzo.md +++ b/src/arazzo.md @@ -979,9 +979,9 @@ The runtime expression is defined by the following [ABNF](https://tools.ietf.org workflow-field-name = identifier ; Source descriptions expressions - source-reference = source-name "." reference-id + source-reference = source-name "." source-reference-id source-name = identifier-strict - reference-id = 1*CHAR + source-reference-id = 1*CHAR ; operationIds have no character restrictions in OpenAPI/AsyncAPI ; Resolution priority defined in spec text: (1) operationId/workflowId, (2) field names