Open netmilk opened 9 years ago
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)Resulting Transaction Path should be much more descriptive and unique. It still won't catch all scenarios though. Example of an edge case:
+ Request
+ Response 200 (application/json)
+ Body ...
+ Response 200 (application/json)
+ Body ...
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 Paths
The 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-pairs
Is 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.
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:
Examples
1. Full notation with multiple request-response pairs
Transaction origin object:
Compiled canonical path:
2. Full notation without group
Transaction origin object:
Compiled canonical path:
3. Full notation without group and API name
Transaction origin object:
Compiled canonical path:
4. Simplified notation
Transaction origin object:
Compiled canonical path: