Handing "Cyclical" Relationships #61
Locked
jarrodparkes
started this conversation in
Ideas
Replies: 1 comment
|
This has been resolved... // Does this object contain an unresolved "$ref"? This occurs when `cebe\openapi\Reader`
// encounters a cyclical reference. Skip it.
if (data_get($attributes, '$ref')) {
break;
} |
0 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Intro
In our spec, we have a cyclical relationship expressed as...
Listing --> Order --> Listing --> Order ...Listing.json
{ "title": "Listing", "type": "object", "description": "A real-estate listing.", "properties": { "id": { "type": "string" }, "orders": { "type": "array", "items": { "$ref": "./Order.json" } } } }Order.json
{ "title": "Order", "type": "object", "description": "A payment request for some content or service.", "properties": { "id": { "type": "string" }, "title": { "type": "string" }, "listing": { "$ref": "./Listing.json" } } }Our concrete API implementation avoids the cyclical relationship by not allowing a client to access a
Listing --> Order --> Listingrelationship. However, the models are still specified as seen above so we can procedurally generate SDK's with singleListingandOrderobjects. For all the objects in our SDK's, the sub-relationships are generated as optional/nullable properties.Observation 1 🧐
When trying to use Spectator to validate an endpoint like
GET /listingswe get the following error message:This error is caused by Spectator's
AbstractValidator::wrapAttributesToArrayfunction. I've added a few comments to the function based on my understanding:And the line of code causing
ErrorException: Undefined property: stdClass::$type:But this still isn't the whole story. Why is
$typeundefined? We have to dig further...Observation 2 🧐
Spectator depends on a package called
cebe/php-openapi. This package is responsible for reading a JSON/YAML spec file into a usable PHP object. When the reading is performed,cebe\openapi\Readerattempts to replace all$refs with their corresponding structures — similar to how a compiler replaces#definestatements. But, when a cyclical relationship is encountered,cebe\openapi\Readerdoes not replace$refs with their corresponding objects. For our cyclical example, the resulting structure looks like...{ +"title": "Listing" +"type": "object" +"properties": { +"id": { ... } +"orders": { +"type": "array" +"items": { +"properties": { +"id": { ... } +"title": { ... } +"listing": { +"$ref": "#/paths/~1listings/get/responses/200/content/application~1json/schema/properties/data/items/properties/order/properties/listing" } } } } } }Notice how the
listing.$refproperty is not replaced. Instead, the$refremains and its value is replaced with an internal represention of the reference. And this makes sense. Ifcebe\openapi\Readertried to replace cyclical$refs, then it would run forever in an infinite loop.Observation 3 🧐
If we go back to Spectator's
AbstractValidator::wrapAttributesToArrayfunction, recall this line of code:Spectator makes the assumption that all JSON objects have a
$type. But, when$refs are not replaced bycebe\openapi\Reader, that assumption is broken, and we get the original error:Discussion
We're left with a few options to fix the error.
$refs to strings{ "id": "...", "orders": [ { "id": "...", "title": "Order 1", "listing": "https://api.domain.test/listing/{id}" }, { "id": "...", "title": "Order 2", "listing": "https://api.domain.test/listing/{id}" } ] }What does everyone think? It seems like the change to Spectator could happen safely, but it is a bit of a rabbit hole.
Related Issues/PRs
All reactions