I propose to start using new syntax for addressing transactions compiled from the blueprint in hooks instead of transaction names. Transaction names will be supported for backward compatibility.
Transaction names were historically intended only as bread crubs — a human readable location of the specific transaction in the blueprint. Transaction name was never meant as a canonical transaction identifier and that's the reason for its terrible indeterminism.
Format will be a concatenation/serialization of the origin object:
: character as a delimiterPossible questions:
# Some API Name
## Group Some Group Name
### Some Resource Name [/resource]
#### Some Action [GET]
+ Request (application/json)
+ Response 200(application/json)
+ Request (application/xml)
+ Response 200 (application/xml)
Transaction origin object:
{
"apiName": "Some API Name",
"resourceGroupName": "Some Group Name",
"resourceName": "Some Resource Name",
"actionName": "Some Action Name",
"exampleName": "Example 2"
}
Compiled canonical path:
Some API Name:Some Group Name:Some Resource Name:Some Action Name:Example 2
# Some API Name
### Some Resource Name [/resource]
#### Some Action [GET]
+ Request (application/json)
+ Response 200 (application/json)
Transaction origin object:
{
"apiName": "Some API Name",
"resourceGroupName": "",
"resourceName": "Some Resource Name",
"actionName": "Some Action Name",
"exampleName": "Example 1"
}
Compiled canonical path:
Some API Name::Some Resource Name:Some Action Name:Example 1
### Some Resource Name [/resource]
#### Some Action [GET]
+ Request (application/json)
+ Response 200 (application/json)
Transaction origin object:
{
"apiName": "",
"resourceGroupName": "",
"resourceName": "Some Resource Name",
"actionName": "Some Action Name",
"exampleName": "Example 1"
}
Compiled canonical path:
::Some Resource Name:Some Action Name:Example 1
# GET /message
+ Response 200 (text/plain)
Hello World
Transaction origin object:
{
"apiName": "",
"resourceGroupName": "",
"resourceName": "/message",
"actionName": "GET",
"exampleName": "Example 1"
}
Compiled canonical path:
::/message:GET:Example 1
Implemented here https://github.com/apiaryio/blueprint-transactions/pull/1. To be implemented in Dredd's hook handler.
I am proposing a slightly different approach.
The proposal below attempts to address following issues:
Example 1, Example 2) and because they're implicit, people do not know their concept well and confuse them with Requests. I removed them from the Transaction Path. (https://github.com/apiaryio/blueprint-transactions/pull/3)``` apib
However, while such thing is valid API Blueprint syntax, it is ambiguous and should be treated as such. Transaction Path won't distinguish between the two Responses and Dredd will refuse to consume such blueprint. That would be considered as correct and expected behavior, because Dredd can't possibly guess what to test.
Canonical, deterministic way how to address a single _HTTP Transaction_ (single Request-Response pair). It can serve as a unique identifier of the HTTP Transaction.
The Transaction Path string is a serialization of the pathOrigin object according to following rules:
: character as a delimiter.\.: are escaped.pathOrigin object and the Transaction Path as an empty string.requestName component is defined as name of the request followed by parentheses containing effective value of the Content-Type header. Any of those two parts can be omitted. If both parts are present, they're separated by a single space character.responseName component is defined as HTTP code of the response followed by parentheses containing effective value of the Content-Type header. The Content-Type part can be omitted. If both parts are present, they're separated by a single space character.# Sample API Name
## Group Sample Group Name
### Sample Resource Name [/resource]
#### Sample Action Name [POST]
+ Request (application/json)
+ Response 200 (application/json)
+ Request (application/xml)
+ Response 200 (application/xml)
Transaction Path Origin:
{
"apiName": "Sample API Name",
"resourceGroupName": "Sample Group Name",
"resourceName": "Sample Resource Name",
"actionName": "Sample Action Name",
"requestName": "(application/xml)",
"responseName": "200 (application/xml)",
}
Transaction Path:
Sample API Name:Sample Group Name:Sample Resource Name:Sample Action Name:(application/xml):200 (application/xml)
# Sample API Name
## Group Sample Group Name
### Sample Resource Name [/resource]
#### Sample Action Name [POST]
+ Response 200
Transaction Path Origin:
{
"apiName": "Sample API Name",
"resourceGroupName": "Sample Group Name",
"resourceName": "Sample Resource Name",
"actionName": "Sample Action Name",
"requestName": "",
"responseName": "200",
}
Transaction Path:
Sample API Name:Sample Group Name:Sample Resource Name:Sample Action Name::200
# Sample API Name
## Group Sample Group Name
### Sample Resource Name [/resource]
#### Sample Action Name [POST]
+ Response 200 (text/plain)
+ Request Sample Request Name (application/json)
+ Request Another Sample Request Name (application/json)
+ Request (application/json)
+ Response 200 (application/json)
+ Response 401 (application/json)
Transaction Path Origin:
{
"apiName": "Sample API Name",
"resourceGroupName": "Sample Group Name",
"resourceName": "Sample Resource Name",
"actionName": "Sample Action Name",
"requestName": "Another Sample Request Name (application/json)",
"responseName": "401 (application/json)",
}
Transaction Path:
Sample API Name:Sample Group Name:Sample Resource Name:Sample Action Name:Another Sample Request Name (application/json):401 (application/json)
# Sample API Name
## Group Sample Group Name
### Sample Resource Name [/resource]
#### Sample Action Name [POST]
+ Response 200 (application/json)
+ Response 401 (application/json)
+ Response 500 (application/json)
+ Request (application/xml)
+ Response 200 (application/xml)
+ Response 500 (application/xml)
Transaction Path Origin:
{
"apiName": "Sample API Name",
"resourceGroupName": "Sample Group Name",
"resourceName": "Sample Resource Name",
"actionName": "Sample Action Name",
"requestName": "",
"responseName": "401 (application/json)",
}
Transaction Path:
Sample API Name:Sample Group Name:Sample Resource Name:Sample Action Name::200 (application/json)
# Sample API Name
## Group Sample Group Name
### Sample Resource Name [/resource]
#### Sample Action Name [POST]
+ Request
+ Headers
X-Request-ID: 30f14c6c1fc85cba12bfd093aa8f90e3
Content-Type: application/hal+json
+ Response 200
Transaction Path Origin:
{
"apiName": "Sample API Name",
"resourceGroupName": "Sample Group Name",
"resourceName": "Sample Resource Name",
"actionName": "Sample Action Name",
"requestName": "(application/hal+json)",
"responseName": "200",
}
Transaction Path:
Sample API Name:Sample Group Name:Sample Resource Name:Sample Action Name::200 (application/json)
# Sample API Name
### Sample Resource Name [/resource]
#### Sample Action Name [POST]
+ Request (application/json)
+ Response 200 (application/json)
Transaction Path Origin:
{
"apiName": "Sample API Name",
"resourceGroupName": "",
"resourceName": "Sample Resource Name",
"actionName": "Sample Action Name",
"requestName": "(application/json)",
"responseName": "200 (application/json)"
}
Transaction Path:
Sample API Name::Sample Resource Name:Sample Action Name:(application/json):200 (application/json)
### Sample Resource Name [/resource]
#### Sample Action Name [POST]
+ Request (application/json)
+ Response 200 (application/json)
Transaction Path Origin:
{
"apiName": "",
"resourceGroupName": "",
"resourceName": "Sample Resource Name",
"actionName": "Sample Action Name",
"requestName": "(application/json)",
"responseName": "200 (application/json)"
}
Transaction Path:
::Sample Resource Name:Sample Action Name:(application/json):200 (application/json)
# My API: Revamp
### Sample Resource Name [/resource]
#### Sample Action Name [POST]
+ Request (application/json)
+ Response 200 (application/json)
Transaction Path Origin:
{
"apiName": "My API: Revamp",
"resourceGroupName": "",
"resourceName": "Sample Resource Name",
"actionName": "Sample Action Name",
"requestName": "(application/json)",
"responseName": "200 (application/json)"
}
Transaction Path:
My API\: Revamp::Sample Resource Name:Sample Action Name:(application/json):200 (application/json)
# GET /message
+ Response 200 (text/plain)
Hello World
Transaction Path Origin:
{
"apiName": "",
"resourceGroupName": "",
"resourceName": "/message",
"actionName": "GET",
"requestName": "",
"responseName": "200 (text/plain)"
}
Transaction Path:
::/message:GET::200 (text/plain)
Thanks @honzajavorek so much for this proposal incorporating solution for identifying multiple request and responses addressing #25 #294 #361 and others
My $0.50 would be:
origin object, please distinguish between the legacy _Transaction Name Origin_ used for the Transaction Names or the actual _Transaction Path Origin_ used for these Canonical Transaction PathsThe _Transaction Name Origin_ and _Transaction Name_ will be still supported but it will be considered as deprecated. The name compilation will be maintained in the reporters not in the transaction compiler in the future.
Regarding the Multiple Request and Responses support:
transaction.skip = true)transaction.skip = false)--unskip-all or --all-pairsIs there a feature to see the transaction origins? E.g. as the transaction names with dredd --names a command dredd --origins. Otherwise I would suggest such feature.
@kaikun213 There is no such feature right now and it wasn't planned. Could you share more about why it would be useful for you?
@honzajavorek I am quite new to dredd and at the moment debugging my apib and hooks. Somehow some requests get not invoked and others get unwillingly invoked twice. So it would be useful to fastly list the origins and convert the used transaction names to origins.
@kaikun213 I still don't quite understand how origins are more useful then the transaction names themselves. The transaction names are just origin object properties joined together. You can list origins and all the other info in the hooks just by inspecting the transaction object.
Introducing the transaction paths suggested in this issue would probably solve your problems though, because from what you write it seems to me you're experiencing some of the non-determinism of transaction names.
Note: We should drop any structure from the transaction path. Its only purpose should be to "hash" individual requests present in the API description into a single human-readable declarative string, in a deterministic way. We should forget about XPath / JSON Pointer. This is mapping, not breadcrumbs.
For cases when this couldn't be deterministic, user should always be able to give the request/response a name in the API description to distinguish them and to address them.
@honzajavorek Is there any movement on this? I'm very interested in clearly defining various request/response pairs for the same 200 status code for contract testing purposes.
@halfvector We had some design sessions around this in April and per my last comment I have a better idea on how it should work. Currently there's no ETA though. It needs finalization of the design before anyone can implement it. I recognize it as one of the largest Dredd pain points, but as such it also requires me to carve out a significant block of uninterrupted time to tackle it.
@honzajavorek Any updates on this? :)
@halfvector Not really at this moment. We decided to focus on reviving and fixing the heart of Dredd's validation, Gavel. Let's see what we're up to after that.
Most helpful comment
@halfvector We had some design sessions around this in April and per my last comment I have a better idea on how it should work. Currently there's no ETA though. It needs finalization of the design before anyone can implement it. I recognize it as one of the largest Dredd pain points, but as such it also requires me to carve out a significant block of uninterrupted time to tackle it.