diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index 81ca1eff..6be43e60 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -93,7 +93,7 @@ jobs: - name: Require newer phpunit/phpunit version run: "composer require phpunit/phpunit '^11.4' --dev --no-interaction --ansi --no-install" - if: matrix.php == '8.3' + if: matrix.php >= '8.3' - name: "Install dependencies with Composer" uses: "ramsey/composer-install@v2" @@ -109,7 +109,7 @@ jobs: - name: PHPUnit tests run: make test-debug - if: matrix.php == '8.3' + if: matrix.php >= '8.3' - name: Code coverage run: make coverage diff --git a/src/json/JsonReference.php b/src/json/JsonReference.php index 5f248881..113cd1ec 100644 --- a/src/json/JsonReference.php +++ b/src/json/JsonReference.php @@ -28,6 +28,14 @@ final class JsonReference implements JsonSerializable * @var JsonPointer */ private $_pointer; + /** + * @var string|null + */ + private $_summary; + /** + * @var string|null + */ + private $_description; /** * Create a JSON Reference instance from a JSON document. @@ -42,7 +50,11 @@ public static function createFromJson(string $json): JsonReference if (!isset($refObject['$ref'])) { throw new MalformedJsonReferenceObjectException('JSON Reference Object must contain the "$ref" member.'); } - return static::createFromReference($refObject['$ref']); + return static::createFromReference( + $refObject['$ref'], + isset($refObject['summary']) ? $refObject['summary'] : null, + isset($refObject['description']) ? $refObject['description'] : null + ); } /** @@ -66,9 +78,14 @@ public static function createFromUri(string $uri, ?JsonPointer $jsonPointer = nu * @return JsonReference * @throws InvalidJsonPointerSyntaxException if an invalid JSON pointer string is passed as part of the fragment section. */ - public static function createFromReference(string $referenceURI): JsonReference - { + public static function createFromReference( + string $referenceURI, + ?string $summary = null, + ?string $description = null + ): JsonReference { $jsonReference = new JsonReference(); + $jsonReference->_summary = $summary; + $jsonReference->_description = $description; if (strpos($referenceURI, '#') !== false) { list($uri, $fragment) = explode('#', $referenceURI, 2); $jsonReference->_uri = $uri; @@ -129,6 +146,10 @@ public function getReference(): string #[\ReturnTypeWillChange] public function jsonSerialize() //: mixed { - return (object)['$ref' => $this->getReference()]; + return (object) array_filter([ + '$ref' => $this->getReference(), + 'summary' => $this->_summary, + 'description' => $this->_description, + ]); } } diff --git a/src/spec/Reference.php b/src/spec/Reference.php index 81f07e87..e784af75 100644 --- a/src/spec/Reference.php +++ b/src/spec/Reference.php @@ -8,7 +8,6 @@ namespace cebe\openapi\spec; use cebe\openapi\DocumentContextInterface; -use cebe\openapi\exceptions\IOException; use cebe\openapi\exceptions\TypeErrorException; use cebe\openapi\exceptions\UnresolvableReferenceException; use cebe\openapi\json\InvalidJsonPointerSyntaxException; @@ -17,7 +16,6 @@ use cebe\openapi\json\NonexistentJsonPointerReferenceException; use cebe\openapi\ReferenceContext; use cebe\openapi\SpecObjectInterface; -use Symfony\Component\Yaml\Yaml; /** * Reference Object @@ -37,6 +35,14 @@ class Reference implements SpecObjectInterface, DocumentContextInterface * @var string */ private $_ref; + /** + * @var string|null + */ + private $_summary; + /** + * @var string|null + */ + private $_description; /** * @var JsonReference|null */ @@ -81,15 +87,33 @@ public function __construct(array $data, ?string $to = null) 'Unable to instantiate Reference Object, value of $ref must be a string.' ); } + if (isset($data['summary']) && !is_string($data['summary'])) { + throw new TypeErrorException( + 'Unable to instantiate Reference Object, value of summary must be a string.' + ); + } + if (isset($data['description']) && !is_string($data['description'])) { + throw new TypeErrorException( + 'Unable to instantiate Reference Object, value of description must be a string.' + ); + } + $this->_to = $to; $this->_ref = $data['$ref']; + $this->_summary = $data['summary'] ?? null; + $this->_description = $data['description'] ?? null; try { - $this->_jsonReference = JsonReference::createFromReference($this->_ref); + $this->_jsonReference = JsonReference::createFromReference( + $this->_ref, + $this->_summary, + $this->_description + ); } catch (InvalidJsonPointerSyntaxException $e) { $this->_errors[] = 'Reference: value of $ref is not a valid JSON pointer: ' . $e->getMessage(); } - if (count($data) !== 1) { - $this->_errors[] = 'Reference: additional properties are given. Only $ref should be set in a Reference Object.'; + + if (!empty(array_diff(array_keys($data), ['$ref', 'summary', 'description']))) { + $this->_errors[] = 'Reference: additional properties are given. Only $ref, summary and description should be set in a Reference Object.'; } } @@ -99,7 +123,11 @@ public function __construct(array $data, ?string $to = null) */ public function getSerializableData() { - return (object) ['$ref' => $this->_ref]; + return (object) array_filter([ + '$ref' => $this->_ref, + 'summary' => $this->_summary, + 'description' => $this->_description, + ]); } /** @@ -135,6 +163,22 @@ public function getReference() return $this->_ref; } + /** + * @return string|null + */ + public function getSummary() + { + return $this->_summary; + } + + /** + * @return string|null + */ + public function getDescription() + { + return $this->_description; + } + /** * @return JsonReference the JSON Reference. */ diff --git a/tests/spec/ReferenceTest.php b/tests/spec/ReferenceTest.php index b94c946e..6ee6c939 100644 --- a/tests/spec/ReferenceTest.php +++ b/tests/spec/ReferenceTest.php @@ -657,4 +657,49 @@ public function testResolveRelativePathAll() } } + public function testReferenceExtraFields() + { + /** @var $openapi OpenApi */ + $openapi = Reader::readFromYaml(<<<'YAML' +openapi: 3.1.0 +info: + title: test api + version: 1.0.0 +components: + schemas: + Pet: + type: object + properties: + id: + type: integer +paths: + '/pet': + get: + responses: + 200: + description: return a pet + content: + 'application/json': + schema: + $ref: "#/components/schemas/Pet" + summary: 'Pet Schema' + description: 'This is a pet schema' +YAML + , OpenApi::class); + + $result = $openapi->validate(); + $this->assertEquals([], $openapi->getErrors()); + $this->assertTrue($result); + + /** @var $petResponse Response */ + $petResponse = $openapi->paths->getPath('/pet')->get->responses['200']; + $this->assertInstanceOf(Reference::class, $ref = $petResponse->content['application/json']->schema); + $this->assertEquals('Pet Schema', $ref->getSummary()); + $this->assertEquals('This is a pet schema', $ref->getDescription()); + + $openapi->resolveReferences(new \cebe\openapi\ReferenceContext($openapi, 'file:///tmp/openapi.yaml')); + + $this->assertInstanceOf(Schema::class, $refSchema = $petResponse->content['application/json']->schema); + $this->assertSame($openapi->components->schemas['Pet'], $refSchema); + } }