From 60f3386161b93585a65604fbb5213d7366c9cbfc Mon Sep 17 00:00:00 2001 From: Arinzechukwu Date: Thu, 21 Sep 2023 14:33:49 +0100 Subject: [PATCH 1/4] Implemented reference context resolution for JSON data read from string --- src/Reader.php | 33 ++++++++++- src/ReferenceContext.php | 79 +++++++++++++++++++++++++- src/spec/Reference.php | 87 ++++++++++++++-------------- tests/spec/OpenApiTest.php | 112 +++++++++++++++++++++++++++++++++++++ tests/spec/SchemaTest.php | 5 +- 5 files changed, 269 insertions(+), 47 deletions(-) diff --git a/src/Reader.php b/src/Reader.php index 99ee5c3..750fff2 100644 --- a/src/Reader.php +++ b/src/Reader.php @@ -23,6 +23,7 @@ class Reader { /** * Populate OpenAPI spec object from JSON data. + * Saves reference context for resolving internal references * @phpstan-template T of SpecObjectInterface * @phpstan-param class-string $baseType * @phpstan-return T @@ -34,11 +35,37 @@ class Reader * The type of the returned object depends on the `$baseType` argument. * @throws TypeErrorException in case invalid spec data is supplied. */ - public static function readFromJson(string $json, string $baseType = OpenApi::class): SpecObjectInterface + public static function readFromJson(string $json, string $baseType = OpenApi::class, bool $resolveReferences = true): SpecObjectInterface { - return new $baseType(json_decode($json, true)); + $spec = static::fromJson($json, $baseType); + $context = ReferenceContext::readFromString($spec, $json); + $context->mode = $resolveReferences; + $context->setDefaultCacheKey($baseType); + $spec->setReferenceContext($context); + if($resolveReferences && $context->hasComponentsRef()){ + $spec->resolveReferences(); + } + return $spec; } + /** + * Populate OpenAPI spec object from JSON data. + * @phpstan-template T of SpecObjectInterface + * @phpstan-param class-string $baseType + * @phpstan-return T + * @param string $json the JSON string to decode. + * @param string $baseType the base Type to instantiate. This must be an instance of [[SpecObjectInterface]]. + * The default is [[OpenApi]] which is the base type of a OpenAPI specification file. + * You may choose a different type if you instantiate objects from sub sections of a specification. + * @return SpecObjectInterface|OpenApi the OpenApi object instance. + * The type of the returned object depends on the `$baseType` argument. + * @throws TypeErrorException in case invalid spec data is supplied. + */ + protected static function fromJson(string $json, string $baseType = OpenApi::class): SpecObjectInterface + { + return new $baseType(json_decode($json, true)); + } + /** * Populate OpenAPI spec object from YAML data. * @phpstan-template T of SpecObjectInterface @@ -89,7 +116,7 @@ public static function readFromJsonFile(string $fileName, string $baseType = Ope $e->fileName = $fileName; throw $e; } - $spec = static::readFromJson($fileContent, $baseType); + $spec = static::fromJson($fileContent, $baseType); $context = new ReferenceContext($spec, $fileName); $spec->setReferenceContext($context); if ($resolveReferences !== false) { diff --git a/src/ReferenceContext.php b/src/ReferenceContext.php index bde0a96..23ac273 100644 --- a/src/ReferenceContext.php +++ b/src/ReferenceContext.php @@ -10,6 +10,7 @@ use cebe\openapi\exceptions\IOException; use cebe\openapi\exceptions\UnresolvableReferenceException; use cebe\openapi\json\JsonPointer; +use cebe\openapi\spec\OpenApi; use cebe\openapi\spec\Reference; use Symfony\Component\Yaml\Yaml; @@ -51,6 +52,21 @@ class ReferenceContext */ private $_cache; + /** + * @var bool checks if content is read from string or file + */ + private $_read_from_string = false; + + /** + * @var string Default cache key for data read from string + */ + private $_string_cache_key = OpenApi::class; + + /** + * @var bool checks if read string contains components + */ + private $_content_has_components = false; + /** * ReferenceContext constructor. @@ -62,7 +78,7 @@ class ReferenceContext public function __construct(?SpecObjectInterface $base, string $uri, $cache = null) { $this->_baseSpec = $base; - $this->_uri = $this->normalizeUri($uri); + $this->_uri = empty($uri) ? static::class : $this->normalizeUri($uri); $this->_cache = $cache ?? new ReferenceContextCache(); if ($cache === null && $base !== null) { $this->_cache->set($this->_uri, null, $base); @@ -210,6 +226,7 @@ public function resolveRelativeUri(string $uri): string */ public function fetchReferencedFile($uri) { + $uri = $this->resolveCacheUri($uri); if ($this->_cache->has('FILE_CONTENT://' . $uri, 'FILE_CONTENT')) { return $this->_cache->get('FILE_CONTENT://' . $uri, 'FILE_CONTENT'); } @@ -221,6 +238,15 @@ public function fetchReferencedFile($uri) throw $e; } // TODO lazy content detection, should be improved + $parsedContent = $this->parseAndCacheContent($content, $uri); + return $parsedContent; + } + + /** + * Parse content from string to either Yaml or Json + */ + protected function parseAndCacheContent($content, $uri): array + { if (strpos(ltrim($content), '{') === 0) { $parsedContent = json_decode($content, true); } else { @@ -229,6 +255,43 @@ public function fetchReferencedFile($uri) $this->_cache->set('FILE_CONTENT://' . $uri, 'FILE_CONTENT', $parsedContent); return $parsedContent; } + + /** + * Prepare content read and cache from JSON or YAML string + */ + public function prepareDataFromString($content) + { + $this->_read_from_string = true; + $parsedContent = $this->parseAndCacheContent($content, $this->resolveCacheUri('')); + + if(array_key_exists('components', $parsedContent) && str_contains($content, '#/components')){ + $this->_content_has_components = true; + } + } + + /** + * Is content from string or file + */ + public function isFromFile(): bool + { + return !$this->_read_from_string; + } + + /** + * Uses default base classname to cache what ever is read + */ + public function setDefaultCacheKey($key = OpenApi::class) + { + $this->_string_cache_key = $key; + } + + /** + * Return result indicating if components and ref is present in read string + */ + public function hasComponentsRef(): bool + { + return $this->_content_has_components; + } /** * Retrieve the referenced data via JSON pointer. @@ -267,4 +330,18 @@ public function resolveReferenceData($uri, JsonPointer $pointer, $data, $toType) return $referencedObject; } + + protected function resolveCacheUri($uri){ + return empty($uri) ? $this->_string_cache_key : $uri; + } + + /** + * Static function that reads from string and initialises base class + */ + public static function readFromString($base, $content): static + { + $context = new static($base, ''); + $context->prepareDataFromString($content); + return $context; + } } diff --git a/src/spec/Reference.php b/src/spec/Reference.php index cda612a..f3191bc 100644 --- a/src/spec/Reference.php +++ b/src/spec/Reference.php @@ -154,7 +154,7 @@ public function setContext(ReferenceContext $context) /** * @return ReferenceContext */ - public function getContext() : ?ReferenceContext + public function getContext(): ?ReferenceContext { return $this->_context; } @@ -186,53 +186,58 @@ public function resolve(ReferenceContext $context = null) return $this; } try { - if ($jsonReference->getDocumentUri() === '') { - if ($context->mode === ReferenceContext::RESOLVE_MODE_INLINE) { - return $this; - } - - // resolve in current document - $baseSpec = $context->getBaseSpec(); - if ($baseSpec !== null) { - // TODO type error if resolved object does not match $this->_to ? - /** @var SpecObjectInterface $referencedObject */ - $referencedObject = $jsonReference->getJsonPointer()->evaluate($baseSpec); - // transitive reference - if ($referencedObject instanceof Reference) { - $referencedObject = $this->resolveTransitiveReference($referencedObject, $context); + if ($context->isFromFile()) { + if ($jsonReference->getDocumentUri() === '') { + if ($context->mode === ReferenceContext::RESOLVE_MODE_INLINE) { + return $this; } - if ($referencedObject instanceof SpecObjectInterface) { - $referencedObject->setReferenceContext($context); + + // resolve in current document + $baseSpec = $context->getBaseSpec(); + if ($baseSpec !== null) { + // TODO type error if resolved object does not match $this->_to ? + /** @var SpecObjectInterface $referencedObject */ + $referencedObject = $jsonReference->getJsonPointer()->evaluate($baseSpec); + // transitive reference + if ($referencedObject instanceof Reference) { + $referencedObject = $this->resolveTransitiveReference($referencedObject, $context); + } + if ($referencedObject instanceof SpecObjectInterface) { + $referencedObject->setReferenceContext($context); + } + return $referencedObject; + } else { + // if current document was loaded via reference, it may be null, + // so we load current document by URI instead. + $jsonReference = JsonReference::createFromUri($context->getUri(), $jsonReference->getJsonPointer()); } - return $referencedObject; - } else { - // if current document was loaded via reference, it may be null, - // so we load current document by URI instead. - $jsonReference = JsonReference::createFromUri($context->getUri(), $jsonReference->getJsonPointer()); } - } - // resolve in external document - $file = $context->resolveRelativeUri($jsonReference->getDocumentUri()); - try { - $referencedDocument = $context->fetchReferencedFile($file); - } catch (\Throwable $e) { - $exception = new UnresolvableReferenceException( - "Failed to resolve Reference '$this->_ref' to $this->_to Object: " . $e->getMessage(), - $e->getCode(), - $e - ); - $exception->context = $this->getDocumentPosition(); - throw $exception; - } + // resolve in external document + $file = $context->resolveRelativeUri($jsonReference->getDocumentUri()); + try { + $referencedDocument = $context->fetchReferencedFile($file); + } catch (\Throwable $e) { + $exception = new UnresolvableReferenceException( + "Failed to resolve Reference '$this->_ref' to $this->_to Object: " . $e->getMessage(), + $e->getCode(), + $e + ); + $exception->context = $this->getDocumentPosition(); + throw $exception; + } - $referencedDocument = $this->adjustRelativeReferences($referencedDocument, $file, null, $context); - $referencedObject = $context->resolveReferenceData($file, $jsonReference->getJsonPointer(), $referencedDocument, $this->_to); + $referencedDocument = $this->adjustRelativeReferences($referencedDocument, $file, null, $context); + $referencedObject = $context->resolveReferenceData($file, $jsonReference->getJsonPointer(), $referencedDocument, $this->_to); - if ($referencedObject instanceof DocumentContextInterface) { - if ($referencedObject->getDocumentPosition() === null && $this->getDocumentPosition() !== null) { - $referencedObject->setDocumentContext($context->getBaseSpec(), $this->getDocumentPosition()); + if ($referencedObject instanceof DocumentContextInterface) { + if ($referencedObject->getDocumentPosition() === null && $this->getDocumentPosition() !== null) { + $referencedObject->setDocumentContext($context->getBaseSpec(), $this->getDocumentPosition()); + } } + } else { + $referencedDocument = $context->fetchReferencedFile(''); + $referencedObject = $context->resolveReferenceData('', $jsonReference->getJsonPointer(), $referencedDocument, $this->_to); } // transitive reference diff --git a/tests/spec/OpenApiTest.php b/tests/spec/OpenApiTest.php index 20b568e..9ce474d 100644 --- a/tests/spec/OpenApiTest.php +++ b/tests/spec/OpenApiTest.php @@ -1,5 +1,6 @@ assertFalse($openapi->validate()); + + //PASS - Contains Description + $openapi = Reader::readFromJson(<<assertTrue($openapi->validate()); + + + //FAILS - Does not contain required description + $openapi = Reader::readFromJson(<<assertFalse($openapi->validate()); + + //PASSES - Contains Description + $openapi = Reader::readFromJson(<<assertTrue($openapi->validate()); + } } diff --git a/tests/spec/SchemaTest.php b/tests/spec/SchemaTest.php index 1600b3b..fc0ffdc 100644 --- a/tests/spec/SchemaTest.php +++ b/tests/spec/SchemaTest.php @@ -3,6 +3,7 @@ use cebe\openapi\Reader; use cebe\openapi\ReferenceContext; use cebe\openapi\spec\Discriminator; +use cebe\openapi\spec\OpenApi; use cebe\openapi\spec\Reference; use cebe\openapi\spec\Schema; use cebe\openapi\spec\Type; @@ -278,7 +279,7 @@ public function testAllOf() } } JSON; - $openApi = Reader::readFromJson($json); + $openApi = Reader::readFromJson($json, OpenApi::class, false); $this->assertInstanceOf(Schema::class, $identifier = $openApi->components->schemas['identifier']); $this->assertInstanceOf(Schema::class, $person = $openApi->components->schemas['person']); @@ -371,7 +372,7 @@ public function testRefAdditionalProperties() } } JSON; - $openApi = Reader::readFromJson($json); + $openApi = Reader::readFromJson($json, OpenApi::class, false); $this->assertInstanceOf(Schema::class, $booleanProperties = $openApi->components->schemas['booleanProperties']); $this->assertInstanceOf(Schema::class, $person = $openApi->components->schemas['person']); From edd73eae913a22db6f0513f5268b65fd24313c26 Mon Sep 17 00:00:00 2001 From: Arinzechukwu Date: Thu, 21 Sep 2023 14:37:23 +0100 Subject: [PATCH 2/4] corrected initialisation of php-fixer --- .php-cs-fixer.cache | 1 + .php_cs.dist => .php-cs-fixer.dist.php | 2 +- Makefile | 2 +- src/json/JsonPointer.php | 10 +++++----- 4 files changed, 8 insertions(+), 7 deletions(-) create mode 100644 .php-cs-fixer.cache rename .php_cs.dist => .php-cs-fixer.dist.php (92%) diff --git a/.php-cs-fixer.cache b/.php-cs-fixer.cache new file mode 100644 index 0000000..3b84012 --- /dev/null +++ b/.php-cs-fixer.cache @@ -0,0 +1 @@ +{"php":"8.1.2-1ubuntu2.14","version":"3.22.0","indent":" ","lineEnding":"\n","rules":{"blank_line_after_namespace":true,"class_definition":true,"constant_case":true,"control_structure_braces":true,"control_structure_continuation_position":true,"curly_braces_position":true,"elseif":true,"function_declaration":true,"indentation_type":true,"line_ending":true,"lowercase_keywords":true,"method_argument_space":{"on_multiline":"ensure_fully_multiline"},"no_break_comment":true,"no_closing_tag":true,"no_multiple_statements_per_line":true,"no_space_around_double_colon":true,"no_spaces_after_function_name":true,"no_spaces_inside_parenthesis":true,"no_trailing_whitespace":true,"no_trailing_whitespace_in_comment":true,"single_blank_line_at_eof":true,"single_class_element_per_statement":{"elements":["property"]},"single_import_per_statement":true,"single_line_after_imports":true,"statement_indentation":true,"switch_case_semicolon_to_colon":true,"switch_case_space":true,"visibility_required":{"elements":["method","property"]},"encoding":true,"full_opening_tag":true,"array_syntax":{"syntax":"short"},"general_phpdoc_annotation_remove":{"annotations":["author"]},"header_comment":{"comment_type":"PHPDoc","header":"@copyright Copyright (c) 2018 Carsten Brandt and contributors\n@license https:\/\/github.com\/cebe\/php-openapi\/blob\/master\/LICENSE"}},"hashes":{"src\/json\/InvalidJsonPointerSyntaxException.php":"b059630f670ab6c4bf703971f250f39b","src\/json\/MalformedJsonReferenceObjectException.php":"4621ba2fb2b625a394a1ec62fe150506","src\/json\/JsonReference.php":"bf7b85716a136d8e9b9897abbe04e7bc","src\/json\/JsonPointer.php":"f9868df455c7839a6c0df4fb54b41221","src\/json\/NonexistentJsonPointerReferenceException.php":"1bd59c41770e48ddd0f48159f52bd0b4","src\/DocumentContextInterface.php":"1a6b549bdb8ead8685e2dc2b3c14c7f2","src\/Writer.php":"3a0e6f09156a72f9f83752bd10bd4abb","src\/SpecObjectInterface.php":"9776bf551d861d5c410ae291fc68d1cf","src\/ReferenceContextCache.php":"dc00d622fbbfdbf61b951ad1e71d4a08","src\/ReferenceContext.php":"bbef1d578acbc50333814a5f7b42f04b","src\/spec\/Contact.php":"76a184db65cc367acb9424aa6c6b19cb","src\/spec\/OpenApi.php":"f49b7a2d1822465dee2273993f7fd7b8","src\/spec\/Paths.php":"95ffb099e8df913716da0d3a6e7d32b9","src\/spec\/PathItem.php":"3826cc2c3e52b9bd8a4e244685688ba8","src\/spec\/Header.php":"cf5f97c8e4ad9eb000cf78fa845f071c","src\/spec\/Responses.php":"5848692505c8140d1cf034d164543f37","src\/spec\/ServerVariable.php":"9eeb968cfea87e4be82a1499667be873","src\/spec\/Type.php":"1d5af494a1de4acbc037f25bd292df63","src\/spec\/Callback.php":"fc09c548f9098a770f6f4d5a3f51deab","src\/spec\/License.php":"501ff38df19aaa4edb50b921249a08a0","src\/spec\/Encoding.php":"a9367aa873b1c0dce50bc8fc4236862c","src\/spec\/Server.php":"d35f24acdbe16b2cd98b47a6e008e220","src\/spec\/Components.php":"0573eeb519b95d305366cbf829b78bd1","src\/spec\/MediaType.php":"f5abf375fcd5d0d7bcf1f5ee2bd0fa36","src\/spec\/Example.php":"051be5b8fc34a206b5b2d887d68aabd4","src\/spec\/SecurityRequirement.php":"e0842f71a000da0296abd69caad4d380","src\/spec\/Discriminator.php":"a97efedec44533611b9ac0da4ef62564","src\/spec\/Info.php":"7c7be9396dde0d0680f749ef3508c56f","src\/spec\/Xml.php":"1206b7747ffaf21d69080224b2b26afe","src\/spec\/Response.php":"d82e6680bd0bd9009d211752dd2e4d42","src\/spec\/OAuthFlows.php":"32a66acafe9610607431dd9e18096a78","src\/spec\/ExternalDocumentation.php":"9658b7b1e300d59326bc28b589c0e04f","src\/spec\/Link.php":"c3281aacfac28326cc2e598c108aa392","src\/spec\/Reference.php":"4da05e8f1aca4e4ac892ed36beda4acf","src\/spec\/OAuthFlow.php":"a4f78014c8fb85690f6d85f0d32f9897","src\/spec\/RequestBody.php":"4bdbfdd4080839d7d117f1fd1dd156a4","src\/spec\/SecurityScheme.php":"f497449130147a454deb5d54aa3adda6","src\/spec\/Operation.php":"f009a989b6f02c9474f49d2812475c77","src\/spec\/Parameter.php":"f2e2e0d9f07bf9bc84434e1c6a20a27d","src\/spec\/Schema.php":"5211cdb25bf26a3e4769c70ee6baffd1","src\/spec\/Tag.php":"4799bcf9b60c89ea96adc7c5c0b6ae7d","src\/Reader.php":"b538cedc036478551dd2921c6a1bc656","src\/exceptions\/ReadonlyPropertyException.php":"73953d7c362ae9c073318d05ca34c0a4","src\/exceptions\/UnknownPropertyException.php":"1da6eb38f5cae64452edfd9429af263a","src\/exceptions\/TypeErrorException.php":"85835ba46ac1609e5d02c266c4ad7ac9","src\/exceptions\/UnresolvableReferenceException.php":"6b3b46d0da94436fd8e4e448eed215de","src\/exceptions\/IOException.php":"a8e782964942ef4ed50c4e514b6a8d08","src\/SpecBaseObject.php":"2cf9645d21b34d6d997e8623c7754001"}} \ No newline at end of file diff --git a/.php_cs.dist b/.php-cs-fixer.dist.php similarity index 92% rename from .php_cs.dist rename to .php-cs-fixer.dist.php index 704f437..3594ae9 100644 --- a/.php_cs.dist +++ b/.php-cs-fixer.dist.php @@ -1,6 +1,6 @@ setRules([ '@PSR2' => true, 'array_syntax' => ['syntax' => 'short'], diff --git a/Makefile b/Makefile index ee99b2a..b4fae49 100644 --- a/Makefile +++ b/Makefile @@ -34,7 +34,7 @@ check-style: php-cs-fixer.phar fix-style: php-cs-fixer.phar $(DOCKER_PHP) vendor/bin/indent --tabs composer.json - $(DOCKER_PHP) vendor/bin/indent --spaces .php_cs.dist + $(DOCKER_PHP) vendor/bin/indent --spaces .php-cs-fixer.dist.php $(DOCKER_PHP) ./php-cs-fixer.phar fix src/ --diff install: composer.lock yarn.lock diff --git a/src/json/JsonPointer.php b/src/json/JsonPointer.php index 0e1f75e..3121e08 100644 --- a/src/json/JsonPointer.php +++ b/src/json/JsonPointer.php @@ -107,11 +107,11 @@ public function evaluate($jsonDocument) foreach ($this->getPath() as $part) { if (is_array($currentReference)) { -// if (!preg_match('~^([1-9]*[0-9]|-)$~', $part)) { -// throw new NonexistentJsonPointerReferenceException( -// "Failed to evaluate pointer '$this->_pointer'. Invalid pointer path '$part' for Array at path '$currentPath'." -// ); -// } + // if (!preg_match('~^([1-9]*[0-9]|-)$~', $part)) { + // throw new NonexistentJsonPointerReferenceException( + // "Failed to evaluate pointer '$this->_pointer'. Invalid pointer path '$part' for Array at path '$currentPath'." + // ); + // } if ($part === '-' || !array_key_exists($part, $currentReference)) { throw new NonexistentJsonPointerReferenceException( "Failed to evaluate pointer '$this->_pointer'. Array has no member $part at path '$currentPath'." From 8536ad9ca1e5835476fdd7b1c049705cfd580815 Mon Sep 17 00:00:00 2001 From: Arinzechukwu Date: Thu, 21 Sep 2023 14:41:42 +0100 Subject: [PATCH 3/4] Fixes code style --- .php-cs-fixer.cache | 2 +- src/Reader.php | 6 +++--- src/ReferenceContext.php | 13 +++++++------ 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/.php-cs-fixer.cache b/.php-cs-fixer.cache index 3b84012..8250796 100644 --- a/.php-cs-fixer.cache +++ b/.php-cs-fixer.cache @@ -1 +1 @@ -{"php":"8.1.2-1ubuntu2.14","version":"3.22.0","indent":" ","lineEnding":"\n","rules":{"blank_line_after_namespace":true,"class_definition":true,"constant_case":true,"control_structure_braces":true,"control_structure_continuation_position":true,"curly_braces_position":true,"elseif":true,"function_declaration":true,"indentation_type":true,"line_ending":true,"lowercase_keywords":true,"method_argument_space":{"on_multiline":"ensure_fully_multiline"},"no_break_comment":true,"no_closing_tag":true,"no_multiple_statements_per_line":true,"no_space_around_double_colon":true,"no_spaces_after_function_name":true,"no_spaces_inside_parenthesis":true,"no_trailing_whitespace":true,"no_trailing_whitespace_in_comment":true,"single_blank_line_at_eof":true,"single_class_element_per_statement":{"elements":["property"]},"single_import_per_statement":true,"single_line_after_imports":true,"statement_indentation":true,"switch_case_semicolon_to_colon":true,"switch_case_space":true,"visibility_required":{"elements":["method","property"]},"encoding":true,"full_opening_tag":true,"array_syntax":{"syntax":"short"},"general_phpdoc_annotation_remove":{"annotations":["author"]},"header_comment":{"comment_type":"PHPDoc","header":"@copyright Copyright (c) 2018 Carsten Brandt and contributors\n@license https:\/\/github.com\/cebe\/php-openapi\/blob\/master\/LICENSE"}},"hashes":{"src\/json\/InvalidJsonPointerSyntaxException.php":"b059630f670ab6c4bf703971f250f39b","src\/json\/MalformedJsonReferenceObjectException.php":"4621ba2fb2b625a394a1ec62fe150506","src\/json\/JsonReference.php":"bf7b85716a136d8e9b9897abbe04e7bc","src\/json\/JsonPointer.php":"f9868df455c7839a6c0df4fb54b41221","src\/json\/NonexistentJsonPointerReferenceException.php":"1bd59c41770e48ddd0f48159f52bd0b4","src\/DocumentContextInterface.php":"1a6b549bdb8ead8685e2dc2b3c14c7f2","src\/Writer.php":"3a0e6f09156a72f9f83752bd10bd4abb","src\/SpecObjectInterface.php":"9776bf551d861d5c410ae291fc68d1cf","src\/ReferenceContextCache.php":"dc00d622fbbfdbf61b951ad1e71d4a08","src\/ReferenceContext.php":"bbef1d578acbc50333814a5f7b42f04b","src\/spec\/Contact.php":"76a184db65cc367acb9424aa6c6b19cb","src\/spec\/OpenApi.php":"f49b7a2d1822465dee2273993f7fd7b8","src\/spec\/Paths.php":"95ffb099e8df913716da0d3a6e7d32b9","src\/spec\/PathItem.php":"3826cc2c3e52b9bd8a4e244685688ba8","src\/spec\/Header.php":"cf5f97c8e4ad9eb000cf78fa845f071c","src\/spec\/Responses.php":"5848692505c8140d1cf034d164543f37","src\/spec\/ServerVariable.php":"9eeb968cfea87e4be82a1499667be873","src\/spec\/Type.php":"1d5af494a1de4acbc037f25bd292df63","src\/spec\/Callback.php":"fc09c548f9098a770f6f4d5a3f51deab","src\/spec\/License.php":"501ff38df19aaa4edb50b921249a08a0","src\/spec\/Encoding.php":"a9367aa873b1c0dce50bc8fc4236862c","src\/spec\/Server.php":"d35f24acdbe16b2cd98b47a6e008e220","src\/spec\/Components.php":"0573eeb519b95d305366cbf829b78bd1","src\/spec\/MediaType.php":"f5abf375fcd5d0d7bcf1f5ee2bd0fa36","src\/spec\/Example.php":"051be5b8fc34a206b5b2d887d68aabd4","src\/spec\/SecurityRequirement.php":"e0842f71a000da0296abd69caad4d380","src\/spec\/Discriminator.php":"a97efedec44533611b9ac0da4ef62564","src\/spec\/Info.php":"7c7be9396dde0d0680f749ef3508c56f","src\/spec\/Xml.php":"1206b7747ffaf21d69080224b2b26afe","src\/spec\/Response.php":"d82e6680bd0bd9009d211752dd2e4d42","src\/spec\/OAuthFlows.php":"32a66acafe9610607431dd9e18096a78","src\/spec\/ExternalDocumentation.php":"9658b7b1e300d59326bc28b589c0e04f","src\/spec\/Link.php":"c3281aacfac28326cc2e598c108aa392","src\/spec\/Reference.php":"4da05e8f1aca4e4ac892ed36beda4acf","src\/spec\/OAuthFlow.php":"a4f78014c8fb85690f6d85f0d32f9897","src\/spec\/RequestBody.php":"4bdbfdd4080839d7d117f1fd1dd156a4","src\/spec\/SecurityScheme.php":"f497449130147a454deb5d54aa3adda6","src\/spec\/Operation.php":"f009a989b6f02c9474f49d2812475c77","src\/spec\/Parameter.php":"f2e2e0d9f07bf9bc84434e1c6a20a27d","src\/spec\/Schema.php":"5211cdb25bf26a3e4769c70ee6baffd1","src\/spec\/Tag.php":"4799bcf9b60c89ea96adc7c5c0b6ae7d","src\/Reader.php":"b538cedc036478551dd2921c6a1bc656","src\/exceptions\/ReadonlyPropertyException.php":"73953d7c362ae9c073318d05ca34c0a4","src\/exceptions\/UnknownPropertyException.php":"1da6eb38f5cae64452edfd9429af263a","src\/exceptions\/TypeErrorException.php":"85835ba46ac1609e5d02c266c4ad7ac9","src\/exceptions\/UnresolvableReferenceException.php":"6b3b46d0da94436fd8e4e448eed215de","src\/exceptions\/IOException.php":"a8e782964942ef4ed50c4e514b6a8d08","src\/SpecBaseObject.php":"2cf9645d21b34d6d997e8623c7754001"}} \ No newline at end of file +{"php":"8.1.2-1ubuntu2.14","version":"3.22.0","indent":" ","lineEnding":"\n","rules":{"blank_line_after_namespace":true,"class_definition":true,"constant_case":true,"control_structure_braces":true,"control_structure_continuation_position":true,"curly_braces_position":true,"elseif":true,"function_declaration":true,"indentation_type":true,"line_ending":true,"lowercase_keywords":true,"method_argument_space":{"on_multiline":"ensure_fully_multiline"},"no_break_comment":true,"no_closing_tag":true,"no_multiple_statements_per_line":true,"no_space_around_double_colon":true,"no_spaces_after_function_name":true,"no_spaces_inside_parenthesis":true,"no_trailing_whitespace":true,"no_trailing_whitespace_in_comment":true,"single_blank_line_at_eof":true,"single_class_element_per_statement":{"elements":["property"]},"single_import_per_statement":true,"single_line_after_imports":true,"statement_indentation":true,"switch_case_semicolon_to_colon":true,"switch_case_space":true,"visibility_required":{"elements":["method","property"]},"encoding":true,"full_opening_tag":true,"array_syntax":{"syntax":"short"},"general_phpdoc_annotation_remove":{"annotations":["author"]},"header_comment":{"comment_type":"PHPDoc","header":"@copyright Copyright (c) 2018 Carsten Brandt and contributors\n@license https:\/\/github.com\/cebe\/php-openapi\/blob\/master\/LICENSE"}},"hashes":{"src\/json\/InvalidJsonPointerSyntaxException.php":"b059630f670ab6c4bf703971f250f39b","src\/json\/MalformedJsonReferenceObjectException.php":"4621ba2fb2b625a394a1ec62fe150506","src\/json\/JsonReference.php":"bf7b85716a136d8e9b9897abbe04e7bc","src\/json\/JsonPointer.php":"f9868df455c7839a6c0df4fb54b41221","src\/json\/NonexistentJsonPointerReferenceException.php":"1bd59c41770e48ddd0f48159f52bd0b4","src\/DocumentContextInterface.php":"1a6b549bdb8ead8685e2dc2b3c14c7f2","src\/Writer.php":"3a0e6f09156a72f9f83752bd10bd4abb","src\/SpecObjectInterface.php":"9776bf551d861d5c410ae291fc68d1cf","src\/ReferenceContextCache.php":"dc00d622fbbfdbf61b951ad1e71d4a08","src\/ReferenceContext.php":"a28d8903982a764c9d1353ff681cef73","src\/spec\/Contact.php":"76a184db65cc367acb9424aa6c6b19cb","src\/spec\/OpenApi.php":"f49b7a2d1822465dee2273993f7fd7b8","src\/spec\/Paths.php":"95ffb099e8df913716da0d3a6e7d32b9","src\/spec\/PathItem.php":"3826cc2c3e52b9bd8a4e244685688ba8","src\/spec\/Header.php":"cf5f97c8e4ad9eb000cf78fa845f071c","src\/spec\/Responses.php":"5848692505c8140d1cf034d164543f37","src\/spec\/ServerVariable.php":"9eeb968cfea87e4be82a1499667be873","src\/spec\/Type.php":"1d5af494a1de4acbc037f25bd292df63","src\/spec\/Callback.php":"fc09c548f9098a770f6f4d5a3f51deab","src\/spec\/License.php":"501ff38df19aaa4edb50b921249a08a0","src\/spec\/Encoding.php":"a9367aa873b1c0dce50bc8fc4236862c","src\/spec\/Server.php":"d35f24acdbe16b2cd98b47a6e008e220","src\/spec\/Components.php":"0573eeb519b95d305366cbf829b78bd1","src\/spec\/MediaType.php":"f5abf375fcd5d0d7bcf1f5ee2bd0fa36","src\/spec\/Example.php":"051be5b8fc34a206b5b2d887d68aabd4","src\/spec\/SecurityRequirement.php":"e0842f71a000da0296abd69caad4d380","src\/spec\/Discriminator.php":"a97efedec44533611b9ac0da4ef62564","src\/spec\/Info.php":"7c7be9396dde0d0680f749ef3508c56f","src\/spec\/Xml.php":"1206b7747ffaf21d69080224b2b26afe","src\/spec\/Response.php":"d82e6680bd0bd9009d211752dd2e4d42","src\/spec\/OAuthFlows.php":"32a66acafe9610607431dd9e18096a78","src\/spec\/ExternalDocumentation.php":"9658b7b1e300d59326bc28b589c0e04f","src\/spec\/Link.php":"c3281aacfac28326cc2e598c108aa392","src\/spec\/Reference.php":"6f6ad7ec8613da186d1ff8e191d64ee7","src\/spec\/OAuthFlow.php":"a4f78014c8fb85690f6d85f0d32f9897","src\/spec\/RequestBody.php":"4bdbfdd4080839d7d117f1fd1dd156a4","src\/spec\/SecurityScheme.php":"f497449130147a454deb5d54aa3adda6","src\/spec\/Operation.php":"f009a989b6f02c9474f49d2812475c77","src\/spec\/Parameter.php":"f2e2e0d9f07bf9bc84434e1c6a20a27d","src\/spec\/Schema.php":"5211cdb25bf26a3e4769c70ee6baffd1","src\/spec\/Tag.php":"4799bcf9b60c89ea96adc7c5c0b6ae7d","src\/Reader.php":"621b1676e35bf3e11994dfb8d5ee094d","src\/exceptions\/ReadonlyPropertyException.php":"73953d7c362ae9c073318d05ca34c0a4","src\/exceptions\/UnknownPropertyException.php":"1da6eb38f5cae64452edfd9429af263a","src\/exceptions\/TypeErrorException.php":"85835ba46ac1609e5d02c266c4ad7ac9","src\/exceptions\/UnresolvableReferenceException.php":"6b3b46d0da94436fd8e4e448eed215de","src\/exceptions\/IOException.php":"a8e782964942ef4ed50c4e514b6a8d08","src\/SpecBaseObject.php":"2cf9645d21b34d6d997e8623c7754001"}} \ No newline at end of file diff --git a/src/Reader.php b/src/Reader.php index 750fff2..bf0306c 100644 --- a/src/Reader.php +++ b/src/Reader.php @@ -42,7 +42,7 @@ public static function readFromJson(string $json, string $baseType = OpenApi::cl $context->mode = $resolveReferences; $context->setDefaultCacheKey($baseType); $spec->setReferenceContext($context); - if($resolveReferences && $context->hasComponentsRef()){ + if($resolveReferences && $context->hasComponentsRef()) { $spec->resolveReferences(); } return $spec; @@ -60,11 +60,11 @@ public static function readFromJson(string $json, string $baseType = OpenApi::cl * @return SpecObjectInterface|OpenApi the OpenApi object instance. * The type of the returned object depends on the `$baseType` argument. * @throws TypeErrorException in case invalid spec data is supplied. - */ + */ protected static function fromJson(string $json, string $baseType = OpenApi::class): SpecObjectInterface { return new $baseType(json_decode($json, true)); - } + } /** * Populate OpenAPI spec object from YAML data. diff --git a/src/ReferenceContext.php b/src/ReferenceContext.php index 23ac273..4fba238 100644 --- a/src/ReferenceContext.php +++ b/src/ReferenceContext.php @@ -59,7 +59,7 @@ class ReferenceContext /** * @var string Default cache key for data read from string - */ + */ private $_string_cache_key = OpenApi::class; /** @@ -264,14 +264,14 @@ public function prepareDataFromString($content) $this->_read_from_string = true; $parsedContent = $this->parseAndCacheContent($content, $this->resolveCacheUri('')); - if(array_key_exists('components', $parsedContent) && str_contains($content, '#/components')){ + if(array_key_exists('components', $parsedContent) && str_contains($content, '#/components')) { $this->_content_has_components = true; } } /** * Is content from string or file - */ + */ public function isFromFile(): bool { return !$this->_read_from_string; @@ -283,7 +283,7 @@ public function isFromFile(): bool public function setDefaultCacheKey($key = OpenApi::class) { $this->_string_cache_key = $key; - } + } /** * Return result indicating if components and ref is present in read string @@ -291,7 +291,7 @@ public function setDefaultCacheKey($key = OpenApi::class) public function hasComponentsRef(): bool { return $this->_content_has_components; - } + } /** * Retrieve the referenced data via JSON pointer. @@ -331,7 +331,8 @@ public function resolveReferenceData($uri, JsonPointer $pointer, $data, $toType) return $referencedObject; } - protected function resolveCacheUri($uri){ + protected function resolveCacheUri($uri) + { return empty($uri) ? $this->_string_cache_key : $uri; } From 4f6192bcee765165e925087fa5f3de6eeae6d393 Mon Sep 17 00:00:00 2001 From: Arinzechukwu Date: Fri, 22 Sep 2023 16:47:47 +0100 Subject: [PATCH 4/4] Implemented reference context resolution for data read from YAML string --- .gitignore | 1 + .php-cs-fixer.cache | 1 - src/Reader.php | 32 +++++++++++-- tests/spec/OpenApiTest.php | 88 +++++++++++++++++++++++++++++++++++- tests/spec/ReferenceTest.php | 8 ++-- 5 files changed, 120 insertions(+), 10 deletions(-) delete mode 100644 .php-cs-fixer.cache diff --git a/.gitignore b/.gitignore index c86af6f..fa1d02a 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ /node_modules /.php_cs.cache +/.php-cs-fixer.cache /.phpunit.result.cache php-cs-fixer.phar diff --git a/.php-cs-fixer.cache b/.php-cs-fixer.cache deleted file mode 100644 index 8250796..0000000 --- a/.php-cs-fixer.cache +++ /dev/null @@ -1 +0,0 @@ -{"php":"8.1.2-1ubuntu2.14","version":"3.22.0","indent":" ","lineEnding":"\n","rules":{"blank_line_after_namespace":true,"class_definition":true,"constant_case":true,"control_structure_braces":true,"control_structure_continuation_position":true,"curly_braces_position":true,"elseif":true,"function_declaration":true,"indentation_type":true,"line_ending":true,"lowercase_keywords":true,"method_argument_space":{"on_multiline":"ensure_fully_multiline"},"no_break_comment":true,"no_closing_tag":true,"no_multiple_statements_per_line":true,"no_space_around_double_colon":true,"no_spaces_after_function_name":true,"no_spaces_inside_parenthesis":true,"no_trailing_whitespace":true,"no_trailing_whitespace_in_comment":true,"single_blank_line_at_eof":true,"single_class_element_per_statement":{"elements":["property"]},"single_import_per_statement":true,"single_line_after_imports":true,"statement_indentation":true,"switch_case_semicolon_to_colon":true,"switch_case_space":true,"visibility_required":{"elements":["method","property"]},"encoding":true,"full_opening_tag":true,"array_syntax":{"syntax":"short"},"general_phpdoc_annotation_remove":{"annotations":["author"]},"header_comment":{"comment_type":"PHPDoc","header":"@copyright Copyright (c) 2018 Carsten Brandt and contributors\n@license https:\/\/github.com\/cebe\/php-openapi\/blob\/master\/LICENSE"}},"hashes":{"src\/json\/InvalidJsonPointerSyntaxException.php":"b059630f670ab6c4bf703971f250f39b","src\/json\/MalformedJsonReferenceObjectException.php":"4621ba2fb2b625a394a1ec62fe150506","src\/json\/JsonReference.php":"bf7b85716a136d8e9b9897abbe04e7bc","src\/json\/JsonPointer.php":"f9868df455c7839a6c0df4fb54b41221","src\/json\/NonexistentJsonPointerReferenceException.php":"1bd59c41770e48ddd0f48159f52bd0b4","src\/DocumentContextInterface.php":"1a6b549bdb8ead8685e2dc2b3c14c7f2","src\/Writer.php":"3a0e6f09156a72f9f83752bd10bd4abb","src\/SpecObjectInterface.php":"9776bf551d861d5c410ae291fc68d1cf","src\/ReferenceContextCache.php":"dc00d622fbbfdbf61b951ad1e71d4a08","src\/ReferenceContext.php":"a28d8903982a764c9d1353ff681cef73","src\/spec\/Contact.php":"76a184db65cc367acb9424aa6c6b19cb","src\/spec\/OpenApi.php":"f49b7a2d1822465dee2273993f7fd7b8","src\/spec\/Paths.php":"95ffb099e8df913716da0d3a6e7d32b9","src\/spec\/PathItem.php":"3826cc2c3e52b9bd8a4e244685688ba8","src\/spec\/Header.php":"cf5f97c8e4ad9eb000cf78fa845f071c","src\/spec\/Responses.php":"5848692505c8140d1cf034d164543f37","src\/spec\/ServerVariable.php":"9eeb968cfea87e4be82a1499667be873","src\/spec\/Type.php":"1d5af494a1de4acbc037f25bd292df63","src\/spec\/Callback.php":"fc09c548f9098a770f6f4d5a3f51deab","src\/spec\/License.php":"501ff38df19aaa4edb50b921249a08a0","src\/spec\/Encoding.php":"a9367aa873b1c0dce50bc8fc4236862c","src\/spec\/Server.php":"d35f24acdbe16b2cd98b47a6e008e220","src\/spec\/Components.php":"0573eeb519b95d305366cbf829b78bd1","src\/spec\/MediaType.php":"f5abf375fcd5d0d7bcf1f5ee2bd0fa36","src\/spec\/Example.php":"051be5b8fc34a206b5b2d887d68aabd4","src\/spec\/SecurityRequirement.php":"e0842f71a000da0296abd69caad4d380","src\/spec\/Discriminator.php":"a97efedec44533611b9ac0da4ef62564","src\/spec\/Info.php":"7c7be9396dde0d0680f749ef3508c56f","src\/spec\/Xml.php":"1206b7747ffaf21d69080224b2b26afe","src\/spec\/Response.php":"d82e6680bd0bd9009d211752dd2e4d42","src\/spec\/OAuthFlows.php":"32a66acafe9610607431dd9e18096a78","src\/spec\/ExternalDocumentation.php":"9658b7b1e300d59326bc28b589c0e04f","src\/spec\/Link.php":"c3281aacfac28326cc2e598c108aa392","src\/spec\/Reference.php":"6f6ad7ec8613da186d1ff8e191d64ee7","src\/spec\/OAuthFlow.php":"a4f78014c8fb85690f6d85f0d32f9897","src\/spec\/RequestBody.php":"4bdbfdd4080839d7d117f1fd1dd156a4","src\/spec\/SecurityScheme.php":"f497449130147a454deb5d54aa3adda6","src\/spec\/Operation.php":"f009a989b6f02c9474f49d2812475c77","src\/spec\/Parameter.php":"f2e2e0d9f07bf9bc84434e1c6a20a27d","src\/spec\/Schema.php":"5211cdb25bf26a3e4769c70ee6baffd1","src\/spec\/Tag.php":"4799bcf9b60c89ea96adc7c5c0b6ae7d","src\/Reader.php":"621b1676e35bf3e11994dfb8d5ee094d","src\/exceptions\/ReadonlyPropertyException.php":"73953d7c362ae9c073318d05ca34c0a4","src\/exceptions\/UnknownPropertyException.php":"1da6eb38f5cae64452edfd9429af263a","src\/exceptions\/TypeErrorException.php":"85835ba46ac1609e5d02c266c4ad7ac9","src\/exceptions\/UnresolvableReferenceException.php":"6b3b46d0da94436fd8e4e448eed215de","src\/exceptions\/IOException.php":"a8e782964942ef4ed50c4e514b6a8d08","src\/SpecBaseObject.php":"2cf9645d21b34d6d997e8623c7754001"}} \ No newline at end of file diff --git a/src/Reader.php b/src/Reader.php index bf0306c..2776313 100644 --- a/src/Reader.php +++ b/src/Reader.php @@ -39,8 +39,8 @@ public static function readFromJson(string $json, string $baseType = OpenApi::cl { $spec = static::fromJson($json, $baseType); $context = ReferenceContext::readFromString($spec, $json); - $context->mode = $resolveReferences; $context->setDefaultCacheKey($baseType); + $context->mode = ReferenceContext::RESOLVE_MODE_INLINE; $spec->setReferenceContext($context); if($resolveReferences && $context->hasComponentsRef()) { $spec->resolveReferences(); @@ -79,7 +79,33 @@ protected static function fromJson(string $json, string $baseType = OpenApi::cla * The type of the returned object depends on the `$baseType` argument. * @throws TypeErrorException in case invalid spec data is supplied. */ - public static function readFromYaml(string $yaml, string $baseType = OpenApi::class): SpecObjectInterface + public static function readFromYaml(string $yaml, string $baseType = OpenApi::class, bool $resolveReferences = true): SpecObjectInterface + { + $spec = static::fromYaml($yaml, $baseType); + $context = ReferenceContext::readFromString($spec, $yaml); + $context->setDefaultCacheKey($baseType); + $context->mode = ReferenceContext::RESOLVE_MODE_INLINE; + $spec->setReferenceContext($context); + if($resolveReferences && $context->hasComponentsRef()) { + $spec->resolveReferences(); + } + return $spec; + } + + /** + * Populate OpenAPI spec object from YAML data. + * @phpstan-template T of SpecObjectInterface + * @phpstan-param class-string $baseType + * @phpstan-return T + * @param string $yaml the YAML string to decode. + * @param string $baseType the base Type to instantiate. This must be an instance of [[SpecObjectInterface]]. + * The default is [[OpenApi]] which is the base type of a OpenAPI specification file. + * You may choose a different type if you instantiate objects from sub sections of a specification. + * @return SpecObjectInterface|OpenApi the OpenApi object instance. + * The type of the returned object depends on the `$baseType` argument. + * @throws TypeErrorException in case invalid spec data is supplied. + */ + public static function fromYaml(string $yaml, string $baseType = OpenApi::class): SpecObjectInterface { return new $baseType(Yaml::parse($yaml)); } @@ -162,7 +188,7 @@ public static function readFromYamlFile(string $fileName, string $baseType = Ope $e->fileName = $fileName; throw $e; } - $spec = static::readFromYaml($fileContent, $baseType); + $spec = static::fromYaml($fileContent, $baseType); $context = new ReferenceContext($spec, $fileName); $spec->setReferenceContext($context); if ($resolveReferences !== false) { diff --git a/tests/spec/OpenApiTest.php b/tests/spec/OpenApiTest.php index 9ce474d..178d759 100644 --- a/tests/spec/OpenApiTest.php +++ b/tests/spec/OpenApiTest.php @@ -235,7 +235,7 @@ public function testSpecs($openApiFile) } - public function testDeepValidationOfApiOperations() + public function testDeepValidationOfApiOperationsJSON() { $openapi = Reader::readFromJson(<<assertTrue($openapi->validate()); - } + } + + public function testDeepValidationOfApiOperationsYAML() + { + $openapi = Reader::readFromYaml(<<<'YAML' +openapi: 3.0.2 +info: + title: My API + version: "1.0.0" +paths: + '/path': + get: + responses: + 200: + $ref: '#/components/schemas/notAResponse' +components: + schemas: + notAResponse: + type: "integer" +YAML); + $this->assertFalse($openapi->validate()); + + //PASS - Contains Description + $openapi = Reader::readFromYaml(<<<'YAML' +openapi: 3.0.2 +info: + title: My API + version: "1.0.0" +paths: + '/path': + get: + responses: + 200: + $ref: '#/components/schemas/notAResponse' +components: + schemas: + notAResponse: + type: "integer" + description: "Test Description" +YAML); + + $this->assertTrue($openapi->validate()); + + //FAILS - Contains Description + $openapi = Reader::readFromYaml(<<<'YAML' +openapi: 3.0.2 +info: + title: My API + version: "1.0.0" +paths: + '/path': + get: + responses: + 200: + content: "A simple response" +components: + schemas: + notAResponse: + type: "integer" + description: "Test Description" +YAML); + + $this->assertFalse($openapi->validate()); + + //FAILS - Contains Description + $openapi = Reader::readFromYaml(<<<'YAML' +openapi: 3.0.2 +info: + title: My API + version: "1.0.0" +paths: + '/path': + get: + responses: + 200: + description: "Test Description" +components: + schemas: + notAResponse: + type: "integer" + description: "Test Description" +YAML); + + $this->assertTrue($openapi->validate()); + } } diff --git a/tests/spec/ReferenceTest.php b/tests/spec/ReferenceTest.php index b94c946..30a552f 100644 --- a/tests/spec/ReferenceTest.php +++ b/tests/spec/ReferenceTest.php @@ -54,7 +54,7 @@ public function testResolveInDocument() 200: $ref: "#/components/responses/Pet" YAML - , OpenApi::class); + , OpenApi::class, false); $result = $openapi->validate(); $this->assertEquals([], $openapi->getErrors()); @@ -113,7 +113,7 @@ public function testResolveCyclicReferenceInDocument() frog: $ref: "#/components/examples/frog-example" YAML - , OpenApi::class); + , OpenApi::class, false); $result = $openapi->validate(); $this->assertEquals([], $openapi->getErrors()); @@ -311,7 +311,7 @@ enum: - "Six" YAML; - $openapi = Reader::readFromYaml($schema); + $openapi = Reader::readFromYaml($schema, OpenApi::class, false); $openapi->resolveReferences(new \cebe\openapi\ReferenceContext($openapi, $this->createFileUri(__DIR__ . '/data/reference/definitions.yaml'))); $this->assertTrue(isset($openapi->components->schemas['Pet'])); @@ -392,7 +392,7 @@ public function testTransitiveReferenceToFile() YAML; - $openapi = Reader::readFromYaml($schema); + $openapi = Reader::readFromYaml($schema, OpenApi::class, false); $openapi->resolveReferences(new \cebe\openapi\ReferenceContext($openapi, $this->createFileUri(__DIR__ . '/data/reference/definitions.yaml'))); $this->assertTrue(isset($openapi->components->schemas['Dog']));