diff --git a/src/SpecBaseObject.php b/src/SpecBaseObject.php index de65c2c..f811067 100644 --- a/src/SpecBaseObject.php +++ b/src/SpecBaseObject.php @@ -411,6 +411,12 @@ public function setDocumentContext(SpecObjectInterface $baseDocument, JsonPointe $this->_baseDocument = $baseDocument; $this->_jsonPointer = $jsonPointer; + // avoid recursion to get stuck in a loop + if ($this->_recursing) { + return; + } + $this->_recursing = true; + foreach ($this->_properties as $property => $value) { if ($value instanceof DocumentContextInterface) { $value->setDocumentContext($baseDocument, $jsonPointer->append($property)); @@ -422,6 +428,8 @@ public function setDocumentContext(SpecObjectInterface $baseDocument, JsonPointe } } } + + $this->_recursing = false; } /** diff --git a/src/spec/Reference.php b/src/spec/Reference.php index 9ab2b7d..b2e30d2 100644 --- a/src/spec/Reference.php +++ b/src/spec/Reference.php @@ -192,12 +192,16 @@ public function resolve(ReferenceContext $context = null) if ($referencedData === null) { return null; } - /** @var $referencedObject SpecObjectInterface */ - $referencedObject = new $this->_to($referencedData); + + // transitive reference + if (isset($referencedData['$ref'])) { + return (new Reference($referencedData, $this->_to))->resolve(new ReferenceContext(null, $file)); + } else { + /** @var $referencedObject SpecObjectInterface */ + $referencedObject = new $this->_to($referencedData); + } if ($jsonReference->getJsonPointer()->getPointer() === '') { $newContext = new ReferenceContext($referencedObject, $file); - $newContext->throwException = $context->throwException; - $referencedObject->setReferenceContext($newContext); if ($referencedObject instanceof DocumentContextInterface) { $referencedObject->setDocumentContext($referencedObject, $jsonReference->getJsonPointer()); } @@ -206,9 +210,9 @@ public function resolve(ReferenceContext $context = null) // the whole document. We do not know the base type of the file at this point, // so base document must be null. $newContext = new ReferenceContext(null, $file); - $newContext->throwException = $context->throwException; - $referencedObject->setReferenceContext($newContext); } + $newContext->throwException = $context->throwException; + $referencedObject->setReferenceContext($newContext); return $referencedObject; } catch (NonexistentJsonPointerReferenceException $e) { diff --git a/tests/spec/ReferenceTest.php b/tests/spec/ReferenceTest.php index 5f563a4..16f3361 100644 --- a/tests/spec/ReferenceTest.php +++ b/tests/spec/ReferenceTest.php @@ -2,6 +2,7 @@ use cebe\openapi\Reader; use cebe\openapi\spec\OpenApi; +use cebe\openapi\spec\Parameter; use cebe\openapi\spec\Reference; use cebe\openapi\spec\RequestBody; use cebe\openapi\spec\Response; @@ -178,17 +179,22 @@ public function testResolveFileInSubdir() $this->assertEquals([], $openapi->getErrors()); $this->assertTrue($result); - $this->assertInstanceOf(Reference::class, $petItems = $openapi->components->schemas['Pet']); - $this->assertInstanceOf(Reference::class, $petItems = $openapi->components->schemas['Dog']); + $this->assertInstanceOf(Reference::class, $openapi->components->schemas['Pet']); + $this->assertInstanceOf(Reference::class, $openapi->components->schemas['Dog']); + $this->assertInstanceOf(Reference::class, $openapi->components->parameters['Parameter.PetId']); $openapi->resolveReferences(new \cebe\openapi\ReferenceContext($openapi, $file)); - $this->assertInstanceOf(Schema::class, $petItems = $openapi->components->schemas['Pet']); - $this->assertInstanceOf(Schema::class, $petItems = $openapi->components->schemas['Dog']); + $this->assertInstanceOf(Schema::class, $openapi->components->schemas['Pet']); + $this->assertInstanceOf(Schema::class, $openapi->components->schemas['Dog']); + $this->assertInstanceOf(Parameter::class, $openapi->components->parameters['Parameter.PetId']); $this->assertArrayHasKey('id', $openapi->components->schemas['Pet']->properties); $this->assertArrayHasKey('name', $openapi->components->schemas['Dog']->properties); + $this->assertEquals('petId', $openapi->components->parameters['Parameter.PetId']->name); + $this->assertInstanceOf(Schema::class, $openapi->components->parameters['Parameter.PetId']->schema); + $this->assertEquals('integer', $openapi->components->parameters['Parameter.PetId']->schema->type); - // second level reference inside of definitions.yaml + // second level references $this->assertArrayHasKey('food', $openapi->components->schemas['Dog']->properties); $this->assertInstanceOf(Schema::class, $openapi->components->schemas['Dog']->properties['food']); $this->assertArrayHasKey('id', $openapi->components->schemas['Dog']->properties['food']->properties); @@ -199,6 +205,13 @@ public function testResolveFileInSubdir() $responseContent = $openapi->paths->getPath('/pets')->get->responses[200]->content['application/json']; $this->assertInstanceOf(Schema::class, $responseContent->schema); $this->assertEquals('A Pet', $responseContent->schema->description); + + // third level reference back to original file + $this->assertCount(1, $parameters = $openapi->paths->getPath('/pets')->get->parameters); + $parameter = reset($parameters); + $this->assertEquals('petId', $parameter->name); + $this->assertInstanceOf(Schema::class, $parameter->schema); + $this->assertEquals('integer', $parameter->schema->type); } public function testResolveFileHttp() diff --git a/tests/spec/data/reference/paths/pets.json b/tests/spec/data/reference/paths/pets.json index 2e6bb58..21d9c34 100644 --- a/tests/spec/data/reference/paths/pets.json +++ b/tests/spec/data/reference/paths/pets.json @@ -1,5 +1,8 @@ { "get": { + "parameters": [ + {"$ref": "../subdir.yaml#/components/parameters/Parameter.PetId"} + ], "responses": { "200": { "description": "return a pet", diff --git a/tests/spec/data/reference/subdir.yaml b/tests/spec/data/reference/subdir.yaml index 3c24e69..12da957 100644 --- a/tests/spec/data/reference/subdir.yaml +++ b/tests/spec/data/reference/subdir.yaml @@ -8,6 +8,9 @@ components: $ref: 'subdir/Pet.yaml' Dog: $ref: 'subdir/Dog.yaml' + parameters: + "Parameter.PetId": + "$ref": "./subdir/Parameter.PetId.json" paths: '/pet': get: diff --git a/tests/spec/data/reference/subdir/Parameter.PetId.json b/tests/spec/data/reference/subdir/Parameter.PetId.json new file mode 100644 index 0000000..dc7bc7e --- /dev/null +++ b/tests/spec/data/reference/subdir/Parameter.PetId.json @@ -0,0 +1,10 @@ +{ + "name": "petId", + "description": "Represents the id of a pet", + "in": "path", + "required": true, + "schema": { + "type": "integer" + }, + "style": "simple" +}