From fb736f73c9cb459a30b872157faf94a3d3efae3d Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Fri, 20 Mar 2020 20:33:28 +0100 Subject: [PATCH 01/39] WIP cache loaded files in reference context simple cache implementation does not yet normalize relative paths in URIs --- src/ReferenceContext.php | 77 ++++++++++++++++++++++++++++++++++- src/ReferenceContextCache.php | 38 +++++++++++++++++ src/spec/Reference.php | 52 +++++------------------ 3 files changed, 125 insertions(+), 42 deletions(-) create mode 100644 src/ReferenceContextCache.php diff --git a/src/ReferenceContext.php b/src/ReferenceContext.php index 7eaab54f..4b55eabf 100644 --- a/src/ReferenceContext.php +++ b/src/ReferenceContext.php @@ -7,7 +7,11 @@ namespace cebe\openapi; +use cebe\openapi\exceptions\IOException; use cebe\openapi\exceptions\UnresolvableReferenceException; +use cebe\openapi\json\JsonPointer; +use cebe\openapi\spec\Reference; +use Symfony\Component\Yaml\Yaml; /** * ReferenceContext represents a context in which references are resolved. @@ -27,17 +31,24 @@ class ReferenceContext * @var string */ private $_uri; + /** + * @var ReferenceContextCache + */ + private $_cache; + /** * ReferenceContext constructor. * @param SpecObjectInterface $base the base object of the spec. * @param string $uri the URI to the base object. + * @param ReferenceContextCache $cache cache instance for storing referenced file data. * @throws UnresolvableReferenceException in case an invalid or non-absolute URI is provided. */ - public function __construct(?SpecObjectInterface $base, string $uri) + public function __construct(?SpecObjectInterface $base, string $uri, $cache = null) { $this->_baseSpec = $base; $this->_uri = $this->normalizeUri($uri); + $this->_cache = $cache ?? new ReferenceContextCache(); } /** @@ -138,4 +149,68 @@ private function dirname($path) } return ''; } + + private $_fileCache; + + /** + * Fetch referenced file by URI. + * + * The current context will cache files by URI, so they are only loaded once. + * + * @throws IOException in case the file is not readable or fetching the file + * from a remote URL failed. + */ + public function fetchReferencedFile($uri) + { + $content = file_get_contents($uri); + if ($content === false) { + $e = new IOException("Failed to read file: '$uri'"); + $e->fileName = $uri; + throw $e; + } + // TODO lazy content detection, should be improved + if (strpos(ltrim($content), '{') === 0) { + return json_decode($content, true); + } else { + return Yaml::parse($content); + } + } + + /** + * Retrieve the referenced data via JSON pointer. + * + * This function caches referenced data to make sure references to the same + * data structures end up being the same object instance in PHP. + * + * @param string $uri + * @param JsonPointer $pointer + * @param array $data + * @param string|null $toType + * @return SpecObjectInterface|array + */ + public function resolveReferenceData($uri, JsonPointer $pointer, $data, $toType) + { + $ref = $uri . '#' . $pointer->getPointer(); + if ($this->_cache->has($ref, $toType)) { + return $this->_cache->get($ref, $toType); + } + + $referencedData = $pointer->evaluate($data); + + if ($referencedData === null) { + return null; + } + + // transitive reference + if (isset($referencedData['$ref'])) { + return (new Reference($referencedData, $toType))->resolve(new ReferenceContext(null, $uri)); + } + /** @var SpecObjectInterface|array $referencedObject */ + $referencedObject = $toType !== null ? new $toType($referencedData) : $referencedData; + + $this->_cache->set($ref, $toType, $referencedObject); + + return $referencedObject; + } + } diff --git a/src/ReferenceContextCache.php b/src/ReferenceContextCache.php new file mode 100644 index 00000000..74ed3028 --- /dev/null +++ b/src/ReferenceContextCache.php @@ -0,0 +1,38 @@ + and contributors + * @license https://github.com/cebe/php-openapi/blob/master/LICENSE + */ + +namespace cebe\openapi; + +/** + * ReferenceContextCache represents a cache storage for caching content of referenced files. + */ +class ReferenceContextCache +{ + private $_cache = []; + + + public function set($ref, $type, $data) + { + $this->_cache[$ref][$type ?? ''] = $data; + + // store fallback value for resolving with unknown type + if ($type !== null && !isset($this->_cache[$ref][''])) { + $this->_cache[$ref][''] = $data; + } + } + + public function get($ref, $type) + { + return $this->_cache[$ref][$type ?? ''] ?? null; + } + + public function has($ref, $type) + { + return isset($this->_cache[$ref]) && + array_key_exists($type ?? '', $this->_cache[$ref]); + } +} diff --git a/src/spec/Reference.php b/src/spec/Reference.php index 1eef2eca..6cebb759 100644 --- a/src/spec/Reference.php +++ b/src/spec/Reference.php @@ -205,20 +205,19 @@ public function resolve(ReferenceContext $context = null) // resolve in external document $file = $context->resolveRelativeUri($jsonReference->getDocumentUri()); - // TODO could be a good idea to cache loaded files in current context to avoid loading the same files over and over again - $referencedDocument = $this->fetchReferencedFile($file); - $referencedData = $jsonReference->getJsonPointer()->evaluate($referencedDocument); - - if ($referencedData === null) { - return null; + 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; } - // transitive reference - if (isset($referencedData['$ref'])) { - return (new Reference($referencedData, $this->_to))->resolve(new ReferenceContext(null, $file)); - } - /** @var SpecObjectInterface|array $referencedObject */ - $referencedObject = $this->_to !== null ? new $this->_to($referencedData) : $referencedData; + $referencedObject = $context->resolveReferenceData($file, $jsonReference->getJsonPointer(), $referencedDocument, $this->_to); if ($jsonReference->getJsonPointer()->getPointer() === '') { $newContext = new ReferenceContext($referencedObject instanceof SpecObjectInterface ? $referencedObject : null, $file); @@ -258,35 +257,6 @@ public function resolve(ReferenceContext $context = null) } } - /** - * @throws UnresolvableReferenceException - */ - private function fetchReferencedFile($uri) - { - try { - $content = file_get_contents($uri); - if ($content === false) { - $e = new IOException("Failed to read file: '$uri'"); - $e->fileName = $uri; - throw $e; - } - // TODO lazy content detection, should probably be improved - if (strpos(ltrim($content), '{') === 0) { - return json_decode($content, true); - } else { - return Yaml::parse($content); - } - } 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; - } - } - /** * Resolves all Reference Objects in this object and replaces them with their resolution. * @throws UnresolvableReferenceException From 518a3d3306760189f79a8d1e0de3b2ddd37d2c81 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Thu, 26 Mar 2020 10:46:03 +0100 Subject: [PATCH 02/39] added failing test --- src/ReferenceContext.php | 9 ++-- src/spec/Reference.php | 4 +- tests/spec/ReferenceTest.php | 44 +++++++++++++++++++ tests/spec/data/reference/models/Cat.yaml | 11 +++++ tests/spec/data/reference/models/Pet.yaml | 8 ++++ tests/spec/data/reference/openapi_models.yaml | 16 +++++++ 6 files changed, 87 insertions(+), 5 deletions(-) create mode 100644 tests/spec/data/reference/models/Cat.yaml create mode 100644 tests/spec/data/reference/models/Pet.yaml create mode 100644 tests/spec/data/reference/openapi_models.yaml diff --git a/src/ReferenceContext.php b/src/ReferenceContext.php index 4b55eabf..0dc99ca9 100644 --- a/src/ReferenceContext.php +++ b/src/ReferenceContext.php @@ -51,6 +51,11 @@ public function __construct(?SpecObjectInterface $base, string $uri, $cache = nu $this->_cache = $cache ?? new ReferenceContextCache(); } + public function getCache(): ReferenceContextCache + { + return $this->_cache; + } + /** * @throws UnresolvableReferenceException in case an invalid or non-absolute URI is provided. */ @@ -150,8 +155,6 @@ private function dirname($path) return ''; } - private $_fileCache; - /** * Fetch referenced file by URI. * @@ -203,7 +206,7 @@ public function resolveReferenceData($uri, JsonPointer $pointer, $data, $toType) // transitive reference if (isset($referencedData['$ref'])) { - return (new Reference($referencedData, $toType))->resolve(new ReferenceContext(null, $uri)); + return (new Reference($referencedData, $toType))->resolve(new ReferenceContext(null, $uri, $this->_cache)); } /** @var SpecObjectInterface|array $referencedObject */ $referencedObject = $toType !== null ? new $toType($referencedData) : $referencedData; diff --git a/src/spec/Reference.php b/src/spec/Reference.php index 6cebb759..bd8262b2 100644 --- a/src/spec/Reference.php +++ b/src/spec/Reference.php @@ -220,7 +220,7 @@ public function resolve(ReferenceContext $context = null) $referencedObject = $context->resolveReferenceData($file, $jsonReference->getJsonPointer(), $referencedDocument, $this->_to); if ($jsonReference->getJsonPointer()->getPointer() === '') { - $newContext = new ReferenceContext($referencedObject instanceof SpecObjectInterface ? $referencedObject : null, $file); + $newContext = new ReferenceContext($referencedObject instanceof SpecObjectInterface ? $referencedObject : null, $file, $context->getCache()); if ($referencedObject instanceof DocumentContextInterface) { $referencedObject->setDocumentContext($referencedObject, $jsonReference->getJsonPointer()); } @@ -228,7 +228,7 @@ public function resolve(ReferenceContext $context = null) // resolving references recursively does not work the same if we have not referenced // 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 = new ReferenceContext(null, $file, $context->getCache()); } $newContext->throwException = $context->throwException; if ($referencedObject instanceof SpecObjectInterface) { diff --git a/tests/spec/ReferenceTest.php b/tests/spec/ReferenceTest.php index 223c4eb9..0aee0f31 100644 --- a/tests/spec/ReferenceTest.php +++ b/tests/spec/ReferenceTest.php @@ -416,4 +416,48 @@ public function testTransitiveReferenceCyclic() $openapi->resolveReferences(new \cebe\openapi\ReferenceContext($openapi, 'file:///tmp/openapi.yaml')); } + + public function testResolveRelativePath() + { + $openapi = Reader::readFromYamlFile(__DIR__ . '/data/reference/openapi_models.yaml'); + + $yaml = \cebe\openapi\Writer::writeToYaml($openapi); + + $this->assertEquals( +<< Date: Thu, 11 Jun 2020 20:08:23 +0200 Subject: [PATCH 03/39] exclusiveMinimum and exclusiveMaximum are boolean (#74) --- src/spec/Schema.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/spec/Schema.php b/src/spec/Schema.php index 8f69a8d5..59eb425c 100644 --- a/src/spec/Schema.php +++ b/src/spec/Schema.php @@ -26,9 +26,9 @@ * @property string $title * @property int|float $multipleOf * @property int|float $maximum - * @property int|float $exclusiveMaximum + * @property bool $exclusiveMaximum * @property int|float $minimum - * @property int|float $exclusiveMinimum + * @property bool $exclusiveMinimum * @property int $maxLength * @property int $minLength * @property string $pattern (This string SHOULD be a valid regular expression, according to the [ECMA 262 regular expression dialect](https://www.ecma-international.org/ecma-262/5.1/#sec-7.8.5)) From 635ba6725f30a17f63396e0fd843e2b4451abc44 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Wed, 8 Jul 2020 10:39:54 +0200 Subject: [PATCH 04/39] WIP --- bin/php-openapi | 1 + src/Reader.php | 24 ++++++--- src/ReferenceContext.php | 91 +++++++++++++++++++++++----------- src/ReferenceContextCache.php | 12 ++++- src/spec/Reference.php | 54 +++++++++++++++++++- tests/ReferenceContextTest.php | 2 +- tests/spec/PathTest.php | 3 +- tests/spec/ReferenceTest.php | 8 +-- 8 files changed, 154 insertions(+), 41 deletions(-) diff --git a/bin/php-openapi b/bin/php-openapi index 81728f12..98c31a8c 100755 --- a/bin/php-openapi +++ b/bin/php-openapi @@ -109,6 +109,7 @@ switch ($command) { $openApi = read_input($inputFile, $inputFormat); $referenceContext = new ReferenceContext($openApi, $inputFile ? realpath($inputFile) : ''); $referenceContext->throwException = false; + $referenceContext->mode = ReferenceContext:: $openApi->resolveReferences($referenceContext); $openApi->setDocumentContext($openApi, new \cebe\openapi\json\JsonPointer('')); diff --git a/src/Reader.php b/src/Reader.php index 08c2996c..143a7b7e 100644 --- a/src/Reader.php +++ b/src/Reader.php @@ -57,9 +57,11 @@ public static function readFromYaml(string $yaml, string $baseType = OpenApi::cl * @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. - * @param bool $resolveReferences whether to automatically resolve references in the specification. + * @param bool|string $resolveReferences whether to automatically resolve references in the specification. * If `true`, all [[Reference]] objects will be replaced with their referenced spec objects by calling * [[SpecObjectInterface::resolveReferences()]]. + * Since version 1.5.0 this can be a string indicating the reference resolving mode: + * - TODO inline vs all * @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. @@ -75,8 +77,12 @@ public static function readFromJsonFile(string $fileName, string $baseType = Ope throw $e; } $spec = static::readFromJson($fileContent, $baseType); - $spec->setReferenceContext(new ReferenceContext($spec, $fileName)); - if ($resolveReferences) { + $context = new ReferenceContext($spec, $fileName); + $spec->setReferenceContext($context); + if ($resolveReferences !== false) { + if (is_string($resolveReferences)) { + $context->mode = $resolveReferences; + } $spec->resolveReferences(); } return $spec; @@ -90,9 +96,11 @@ public static function readFromJsonFile(string $fileName, string $baseType = Ope * @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. - * @param bool $resolveReferences whether to automatically resolve references in the specification. + * @param bool|string $resolveReferences whether to automatically resolve references in the specification. * If `true`, all [[Reference]] objects will be replaced with their referenced spec objects by calling * [[SpecObjectInterface::resolveReferences()]]. + * Since version 1.5.0 this can be a string indicating the reference resolving mode: + * - TODO inline vs all * @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. @@ -108,8 +116,12 @@ public static function readFromYamlFile(string $fileName, string $baseType = Ope throw $e; } $spec = static::readFromYaml($fileContent, $baseType); - $spec->setReferenceContext(new ReferenceContext($spec, $fileName)); - if ($resolveReferences) { + $context = new ReferenceContext($spec, $fileName); + $spec->setReferenceContext($context); + if ($resolveReferences !== false) { + if (is_string($resolveReferences)) { + $context->mode = $resolveReferences; + } $spec->resolveReferences(); } return $spec; diff --git a/src/ReferenceContext.php b/src/ReferenceContext.php index 0dc99ca9..19773eed 100644 --- a/src/ReferenceContext.php +++ b/src/ReferenceContext.php @@ -18,11 +18,26 @@ */ class ReferenceContext { + /** + * only resolve external references. + * The result will be a single API description file with references + * inside of the file structure. + */ + const RESOLVE_MODE_INLINE = 'inline'; + /** + * resolve all references, except recursive ones. + */ + const RESOLVE_MODE_ALL = 'all'; + /** * @var bool whether to throw UnresolvableReferenceException in case a reference can not * be resolved. If `false` errors are added to the Reference Objects error list instead. */ public $throwException = true; + /** + * @var string + */ + public $mode = self::RESOLVE_MODE_ALL; /** * @var SpecObjectInterface */ @@ -49,6 +64,9 @@ public function __construct(?SpecObjectInterface $base, string $uri, $cache = nu $this->_baseSpec = $base; $this->_uri = $this->normalizeUri($uri); $this->_cache = $cache ?? new ReferenceContextCache(); + if ($cache === null) { + $this->_cache->set($uri, null, $base); + } } public function getCache(): ReferenceContextCache @@ -61,6 +79,7 @@ public function getCache(): ReferenceContextCache */ private function normalizeUri($uri) { + // TODO remove dots if (strpos($uri, '://') !== false) { return $uri; } @@ -101,41 +120,55 @@ public function resolveRelativeUri(string $uri): string if (strncmp($baseUri, 'file://', 7) === 0) { if (isset($parts['path'][0]) && $parts['path'][0] === '/') { // absolute path - return 'file://' . $parts['path']; - } - // convert absolute path on windows to a file:// URI. This is probably incomplete but should work with the majority of paths. - if (stripos(PHP_OS, 'WIN') === 0 && strncmp(substr($uri, 1), ':\\', 2) === 0) { - return "file:///" . strtr($uri, [' ' => '%20', '\\' => '/']); - } - - if (isset($parts['path'])) { + $absoluteUri = 'file://' . $this->reduceDots($parts['path']); + } elseif (stripos(PHP_OS, 'WIN') === 0 && strncmp(substr($uri, 1), ':\\', 2) === 0) { + // convert absolute path on windows to a file:// URI. This is probably incomplete but should work with the majority of paths. + $absoluteUri = "file:///" . strtr($uri, [' ' => '%20', '\\' => '/']); + } elseif (isset($parts['path'])) { // relative path - return $this->dirname($baseUri) . '/' . $parts['path']; + $absoluteUri = $this->reduceDots($this->dirname($baseUri) . '/' . $parts['path']); + } else { + throw new UnresolvableReferenceException("Invalid URI: '$uri'"); + } + } else { + $baseParts = parse_url($baseUri); + $absoluteUri = implode('', [ + $baseParts['scheme'], + '://', + isset($baseParts['username']) ? $baseParts['username'] . ( + isset($baseParts['password']) ? ':' . $baseParts['password'] : '' + ) . '@' : '', + $baseParts['host'] ?? '', + isset($baseParts['port']) ? ':' . $baseParts['port'] : '', + ]); + if (isset($parts['path'][0]) && $parts['path'][0] === '/') { + $absoluteUri .= $this->reduceDots($parts['path']); + } elseif (isset($parts['path'])) { + $absoluteUri .= $this->reduceDots(rtrim($this->dirname($baseParts['path'] ?? ''), '/') . '/' . $parts['path']); } - - throw new UnresolvableReferenceException("Invalid URI: '$uri'"); - } - - $baseParts = parse_url($baseUri); - $absoluteUri = implode('', [ - $baseParts['scheme'], - '://', - isset($baseParts['username']) ? $baseParts['username'] . ( - isset($baseParts['password']) ? ':' . $baseParts['password'] : '' - ) . '@' : '', - $baseParts['host'] ?? '', - isset($baseParts['port']) ? ':' . $baseParts['port'] : '', - ]); - if (isset($parts['path'][0]) && $parts['path'][0] === '/') { - $absoluteUri .= $parts['path']; - } elseif (isset($parts['path'])) { - $absoluteUri .= rtrim($this->dirname($baseParts['path'] ?? ''), '/') . '/' . $parts['path']; } return $absoluteUri . (isset($parts['query']) ? '?' . $parts['query'] : '') . (isset($parts['fragment']) ? '#' . $parts['fragment'] : ''); } + private function reduceDots($uri) + { + $parts = explode('/', $uri); + $c = count($parts); + for($i = 0; $i < $c; $i++) { + if ($parts[$i] === '.') { + unset($parts[$i]); + continue; + } + if ($i > 0 && $parts[$i] === '..' && $parts[$i-1] !== '..') { + unset($parts[$i-1]); + unset($parts[$i]); + } + } + return implode('/', $parts); + } + /** * Returns parent directory's path. * This method is similar to `dirname()` except that it will treat @@ -206,7 +239,9 @@ public function resolveReferenceData($uri, JsonPointer $pointer, $data, $toType) // transitive reference if (isset($referencedData['$ref'])) { - return (new Reference($referencedData, $toType))->resolve(new ReferenceContext(null, $uri, $this->_cache)); + $subContext = new ReferenceContext(null, $uri, $this->_cache); + $subContext->mode = $this->mode; + return (new Reference($referencedData, $toType))->resolve($subContext); } /** @var SpecObjectInterface|array $referencedObject */ $referencedObject = $toType !== null ? new $toType($referencedData) : $referencedData; diff --git a/src/ReferenceContextCache.php b/src/ReferenceContextCache.php index 74ed3028..37ec4e35 100644 --- a/src/ReferenceContextCache.php +++ b/src/ReferenceContextCache.php @@ -13,9 +13,10 @@ class ReferenceContextCache { private $_cache = []; +// private $_isbase = []; - public function set($ref, $type, $data) + public function set($ref, $type, $data)//, $isbase = false) { $this->_cache[$ref][$type ?? ''] = $data; @@ -23,8 +24,17 @@ public function set($ref, $type, $data) if ($type !== null && !isset($this->_cache[$ref][''])) { $this->_cache[$ref][''] = $data; } + +// if ($isbase) { +// $this->_isbase[$ref] = true; +// } } +// public function isBase($ref) +// { +// return $this->_isbase[$ref] ?? false; +// } + public function get($ref, $type) { return $this->_cache[$ref][$type ?? ''] ?? null; diff --git a/src/spec/Reference.php b/src/spec/Reference.php index bd8262b2..b44b8fed 100644 --- a/src/spec/Reference.php +++ b/src/spec/Reference.php @@ -58,6 +58,7 @@ public function __construct(array $data, string $to = null) ); } if (!is_string($data['$ref'])) { + print_r($data); throw new TypeErrorException( 'Unable to instantiate Reference Object, value of $ref must be a string.' ); @@ -168,6 +169,11 @@ public function resolve(ReferenceContext $context = null) } try { if ($jsonReference->getDocumentUri() === '') { + + if ($context->mode === ReferenceContext::RESOLVE_MODE_INLINE) { + return $this; + } + // resolve in current document $baseSpec = $context->getBaseSpec(); if ($baseSpec !== null) { @@ -205,6 +211,7 @@ public function resolve(ReferenceContext $context = null) // resolve in external document $file = $context->resolveRelativeUri($jsonReference->getDocumentUri()); +// echo $file . "\n"; try { $referencedDocument = $context->fetchReferencedFile($file); } catch (\Throwable $e) { @@ -216,10 +223,17 @@ public function resolve(ReferenceContext $context = null) $exception->context = $this->getDocumentPosition(); throw $exception; } + echo "\n\n" . $context->getUri() . "\n" . $file . "\n"; + echo json_encode($referencedDocument) . "\n"; + echo $context->mode; + $referencedDocument = $this->adjustRelativeReferences($referencedDocument, $file, null, $context); +// echo json_encode($referencedDocument, JSON_PRETTY_PRINT); $referencedObject = $context->resolveReferenceData($file, $jsonReference->getJsonPointer(), $referencedDocument, $this->_to); - if ($jsonReference->getJsonPointer()->getPointer() === '') { + if ($context->getUri() === $file) { + $newContext = $context; + } elseif ($jsonReference->getJsonPointer()->getPointer() === '') { $newContext = new ReferenceContext($referencedObject instanceof SpecObjectInterface ? $referencedObject : null, $file, $context->getCache()); if ($referencedObject instanceof DocumentContextInterface) { $referencedObject->setDocumentContext($referencedObject, $jsonReference->getJsonPointer()); @@ -231,6 +245,7 @@ public function resolve(ReferenceContext $context = null) $newContext = new ReferenceContext(null, $file, $context->getCache()); } $newContext->throwException = $context->throwException; + $newContext->mode = $context->mode; if ($referencedObject instanceof SpecObjectInterface) { $referencedObject->setReferenceContext($newContext); } @@ -257,6 +272,43 @@ public function resolve(ReferenceContext $context = null) } } + // adjust relative refernces inside of the file to match the context of the base file + private function adjustRelativeReferences($referencedDocument, $basePath, $baseDocument = null, $oContext = null) + { + $context = new ReferenceContext(null, $basePath); + if ($baseDocument === null) { + $baseDocument = $referencedDocument; + } + + foreach($referencedDocument as $key => $value) { + if ($key === '$ref' && is_string($value)) { + if (isset($value[0]) && $value[0] === '#') { + echo "resolbin....\n"; +// if ($oContext === null || $oContext->mode !== ReferenceContext::RESOLVE_MODE_INLINE) { + // direcly inline references in the same document, + // these are not going to be valid in the new context anymore + $referencedDocument = (new JsonPointer(substr($value, 1)))->evaluate($baseDocument); + break; +// } + } +// echo "\n\n$value\n"; + $referencedDocument[$key] = $context->resolveRelativeUri($value); + $parts = explode('#', $referencedDocument[$key], 2); + if ($parts[0] === $oContext->getUri()) { + $referencedDocument[$key] = '#' . ($parts[1] ?? ''); + } + echo $referencedDocument[$key]; +// echo "$referencedDocument[$key]\n"; + + continue; + } + if (is_array($value)) { + $referencedDocument[$key] = $this->adjustRelativeReferences($value, $basePath, $baseDocument, $oContext); + } + } + return $referencedDocument; + } + /** * Resolves all Reference Objects in this object and replaces them with their resolution. * @throws UnresolvableReferenceException diff --git a/tests/ReferenceContextTest.php b/tests/ReferenceContextTest.php index 4934da95..73014fc8 100644 --- a/tests/ReferenceContextTest.php +++ b/tests/ReferenceContextTest.php @@ -32,7 +32,7 @@ public function uriProvider() [ 'https://example.com/api/openapi.yaml', // base URI '../definitions.yaml', // referenced URI - 'https://example.com/api/../definitions.yaml', // expected result + 'https://example.com/definitions.yaml', // expected result ], [ '/var/www/openapi.yaml', // base URI diff --git a/tests/spec/PathTest.php b/tests/spec/PathTest.php index aa065a64..8368f784 100644 --- a/tests/spec/PathTest.php +++ b/tests/spec/PathTest.php @@ -162,7 +162,8 @@ public function testPathItemReference() $this->assertEquals('getBar', $ReferencedBarPath->get->operationId); $this->assertInstanceOf(Reference::class, $ReferencedBarPath->get->responses['200']); - $this->assertInstanceOf(Reference::class, $ReferencedBarPath->get->responses['404']); + $this->assertInstanceOf(Response::class, $ReferencedBarPath->get->responses['404']); + $this->assertEquals('non-existing resource', $barPath->get->responses['404']->description); /** @var $openapi OpenApi */ $openapi = Reader::readFromYamlFile($file, \cebe\openapi\spec\OpenApi::class, true); diff --git a/tests/spec/ReferenceTest.php b/tests/spec/ReferenceTest.php index 0aee0f31..56150d57 100644 --- a/tests/spec/ReferenceTest.php +++ b/tests/spec/ReferenceTest.php @@ -419,7 +419,7 @@ public function testTransitiveReferenceCyclic() public function testResolveRelativePath() { - $openapi = Reader::readFromYamlFile(__DIR__ . '/data/reference/openapi_models.yaml'); + $openapi = Reader::readFromYamlFile(__DIR__ . '/data/reference/openapi_models.yaml', OpenApi::class, \cebe\openapi\ReferenceContext::RESOLVE_MODE_INLINE); $yaml = \cebe\openapi\Writer::writeToYaml($openapi); @@ -444,7 +444,7 @@ public function testResolveRelativePath() type: integer format: int64 cat: - \$ref: '#/components/schemas/Cat' + \$ref: '#/components/schemas/Cat' description: 'A Pet' Cat: type: object @@ -455,9 +455,11 @@ public function testResolveRelativePath() name: type: string description: 'the cats name' + pet: + \$ref: '#/components/schemas/Pet' description: 'A Cat' YAML - , $yaml); + , $yaml, $yaml); } } From 23b9f33f11c7f41648038a1fd2d306ec9ef7245f Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Fri, 10 Jul 2020 21:22:56 +0200 Subject: [PATCH 05/39] refactor reference handling --- Makefile | 18 ++- bin/php-openapi | 9 +- composer.json | 2 +- phpunit.xml.dist | 3 + src/Reader.php | 3 + src/ReferenceContext.php | 148 ++++++++++-------- src/ReferenceContextCache.php | 12 +- src/spec/PathItem.php | 6 + src/spec/Reference.php | 87 +++++----- tests/ReferenceContextTest.php | 130 ++++++++++++++- tests/spec/PathTest.php | 8 +- tests/spec/ReferenceTest.php | 96 +++++++++++- tests/spec/data/reference/structure.yaml | 9 ++ tests/spec/data/reference/structure/paths.yml | 5 + .../data/reference/structure/paths/cat.yml | 4 + .../data/reference/structure/paths/pet.yml | 4 + 16 files changed, 401 insertions(+), 143 deletions(-) create mode 100644 tests/spec/data/reference/structure.yaml create mode 100644 tests/spec/data/reference/structure/paths.yml create mode 100644 tests/spec/data/reference/structure/paths/cat.yml create mode 100644 tests/spec/data/reference/structure/paths/pet.yml diff --git a/Makefile b/Makefile index 04daecd4..627ad781 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,10 @@ TESTCASE= +XDEBUG=0 PHPARGS=-dmemory_limit=64M -#PHPARGS=-dmemory_limit=64M -dzend_extension=xdebug.so -dxdebug.remote_enable=1 +XPHPARGS= +ifeq ($(XDEBUG),1) +XPHPARGS=-dzend_extension=xdebug.so -dxdebug.remote_enable=1 -dxdebug.remote_autostart=1 +endif all: @@ -17,14 +21,14 @@ install: yarn install test: - php $(PHPARGS) vendor/bin/phpunit --verbose $(TESTCASE) - php $(PHPARGS) bin/php-openapi validate tests/spec/data/recursion.json - php $(PHPARGS) bin/php-openapi validate tests/spec/data/recursion2.yaml + php $(PHPARGS) $(XPHPARGS) vendor/bin/phpunit --verbose $(TESTCASE) + php $(PHPARGS) $(XPHPARGS) bin/php-openapi validate tests/spec/data/recursion.json + php $(PHPARGS) $(XPHPARGS) bin/php-openapi validate tests/spec/data/recursion2.yaml lint: - php $(PHPARGS) bin/php-openapi validate tests/spec/data/reference/playlist.json - php $(PHPARGS) bin/php-openapi validate tests/spec/data/recursion.json - php $(PHPARGS) bin/php-openapi validate tests/spec/data/recursion2.yaml + php $(PHPARGS) $(XPHPARGS) bin/php-openapi validate tests/spec/data/reference/playlist.json + php $(PHPARGS) $(XPHPARGS) bin/php-openapi validate tests/spec/data/recursion.json + php $(PHPARGS) $(XPHPARGS) bin/php-openapi validate tests/spec/data/recursion2.yaml node_modules/.bin/speccy lint tests/spec/data/reference/playlist.json node_modules/.bin/speccy lint tests/spec/data/recursion.json diff --git a/bin/php-openapi b/bin/php-openapi index 98c31a8c..00730ef6 100755 --- a/bin/php-openapi +++ b/bin/php-openapi @@ -109,7 +109,9 @@ switch ($command) { $openApi = read_input($inputFile, $inputFormat); $referenceContext = new ReferenceContext($openApi, $inputFile ? realpath($inputFile) : ''); $referenceContext->throwException = false; - $referenceContext->mode = ReferenceContext:: + // TODO apply reference context mode +// $referenceContext->mode = ReferenceContext::RESOLVE_MODE_ALL; +// $referenceContext->mode = ReferenceContext::RESOLVE_MODE_INLINE; $openApi->resolveReferences($referenceContext); $openApi->setDocumentContext($openApi, new \cebe\openapi\json\JsonPointer('')); @@ -187,6 +189,11 @@ switch ($command) { $openApi = read_input($inputFile, $inputFormat); try { + // TODO apply reference context mode +// $referenceContext->mode = ReferenceContext::RESOLVE_MODE_ALL; +// $referenceContext->mode = ReferenceContext::RESOLVE_MODE_INLINE; + // set document context for correctly converting recursive references + $openApi->setDocumentContext($openApi, new \cebe\openapi\json\JsonPointer('')); $openApi->resolveReferences(); } catch (\cebe\openapi\exceptions\UnresolvableReferenceException $e) { error("[\e[33m{$e->context}\e[0m] " . $e->getMessage()); diff --git a/composer.json b/composer.json index d1c98fb2..174bd565 100644 --- a/composer.json +++ b/composer.json @@ -41,7 +41,7 @@ }, "extra": { "branch-alias": { - "dev-master": "1.4.x-dev" + "dev-master": "1.5.x-dev" } }, "bin": [ diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 667ac5f3..de50cde0 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -11,6 +11,9 @@ + + ./src + ./vendor ./tests diff --git a/src/Reader.php b/src/Reader.php index 143a7b7e..b52ebe13 100644 --- a/src/Reader.php +++ b/src/Reader.php @@ -10,6 +10,7 @@ use cebe\openapi\exceptions\IOException; use cebe\openapi\exceptions\TypeErrorException; use cebe\openapi\exceptions\UnresolvableReferenceException; +use cebe\openapi\json\JsonPointer; use cebe\openapi\spec\OpenApi; use Symfony\Component\Yaml\Yaml; @@ -83,6 +84,7 @@ public static function readFromJsonFile(string $fileName, string $baseType = Ope if (is_string($resolveReferences)) { $context->mode = $resolveReferences; } + $spec->setDocumentContext($spec, new JsonPointer('')); $spec->resolveReferences(); } return $spec; @@ -122,6 +124,7 @@ public static function readFromYamlFile(string $fileName, string $baseType = Ope if (is_string($resolveReferences)) { $context->mode = $resolveReferences; } + $spec->setDocumentContext($spec, new JsonPointer('')); $spec->resolveReferences(); } return $spec; diff --git a/src/ReferenceContext.php b/src/ReferenceContext.php index 19773eed..e78d3fdc 100644 --- a/src/ReferenceContext.php +++ b/src/ReferenceContext.php @@ -64,8 +64,8 @@ public function __construct(?SpecObjectInterface $base, string $uri, $cache = nu $this->_baseSpec = $base; $this->_uri = $this->normalizeUri($uri); $this->_cache = $cache ?? new ReferenceContextCache(); - if ($cache === null) { - $this->_cache->set($uri, null, $base); + if ($cache === null && $base !== null) { + $this->_cache->set($this->_uri, null, $base); } } @@ -79,82 +79,41 @@ public function getCache(): ReferenceContextCache */ private function normalizeUri($uri) { - // TODO remove dots if (strpos($uri, '://') !== false) { - return $uri; + $parts = parse_url($uri); + if (isset($parts['path'])) { + $parts['path'] = $this->reduceDots($parts['path']); + } + return $this->buildUri($parts); } if (strncmp($uri, '/', 1) === 0) { + $uri = $this->reduceDots($uri); return "file://$uri"; } if (stripos(PHP_OS, 'WIN') === 0 && strncmp(substr($uri, 1), ':\\', 2) === 0) { + $uri = $this->reduceDots($uri); return "file:///" . strtr($uri, [' ' => '%20', '\\' => '/']); } throw new UnresolvableReferenceException('Can not resolve references for a specification given as a relative path.'); } - public function getBaseSpec(): ?SpecObjectInterface - { - return $this->_baseSpec; - } - - public function getUri(): string - { - return $this->_uri; - } - - /** - * Resolve a relative URI to an absolute URI in the current context. - * @param string $uri - * @throws UnresolvableReferenceException - * @return string - */ - public function resolveRelativeUri(string $uri): string + private function buildUri($parts) { - $parts = parse_url($uri); - if (isset($parts['scheme'])) { - // absolute URL - return $uri; - } - - $baseUri = $this->getUri(); - if (strncmp($baseUri, 'file://', 7) === 0) { - if (isset($parts['path'][0]) && $parts['path'][0] === '/') { - // absolute path - $absoluteUri = 'file://' . $this->reduceDots($parts['path']); - } elseif (stripos(PHP_OS, 'WIN') === 0 && strncmp(substr($uri, 1), ':\\', 2) === 0) { - // convert absolute path on windows to a file:// URI. This is probably incomplete but should work with the majority of paths. - $absoluteUri = "file:///" . strtr($uri, [' ' => '%20', '\\' => '/']); - } elseif (isset($parts['path'])) { - // relative path - $absoluteUri = $this->reduceDots($this->dirname($baseUri) . '/' . $parts['path']); - } else { - throw new UnresolvableReferenceException("Invalid URI: '$uri'"); - } - } else { - $baseParts = parse_url($baseUri); - $absoluteUri = implode('', [ - $baseParts['scheme'], - '://', - isset($baseParts['username']) ? $baseParts['username'] . ( - isset($baseParts['password']) ? ':' . $baseParts['password'] : '' - ) . '@' : '', - $baseParts['host'] ?? '', - isset($baseParts['port']) ? ':' . $baseParts['port'] : '', - ]); - if (isset($parts['path'][0]) && $parts['path'][0] === '/') { - $absoluteUri .= $this->reduceDots($parts['path']); - } elseif (isset($parts['path'])) { - $absoluteUri .= $this->reduceDots(rtrim($this->dirname($baseParts['path'] ?? ''), '/') . '/' . $parts['path']); - } - } - return $absoluteUri - . (isset($parts['query']) ? '?' . $parts['query'] : '') - . (isset($parts['fragment']) ? '#' . $parts['fragment'] : ''); + $scheme = !empty($parts['scheme']) ? $parts['scheme'] . '://' : ''; + $host = $parts['host'] ?? ''; + $port = !empty($parts['port']) ? ':' . $parts['port'] : ''; + $user = $parts['user'] ?? ''; + $pass = !empty($parts['pass']) ? ':' . $parts['pass'] : ''; + $pass = ($user || $pass) ? "$pass@" : ''; + $path = $parts['path'] ?? ''; + $query = !empty($parts['query']) ? '?' . $parts['query'] : ''; + $fragment = !empty($parts['fragment']) ? '#' . $parts['fragment'] : ''; + return "$scheme$user$pass$host$port$path$query$fragment"; } - private function reduceDots($uri) + private function reduceDots($path) { - $parts = explode('/', $uri); + $parts = explode('/', ltrim($path, '/')); $c = count($parts); for($i = 0; $i < $c; $i++) { if ($parts[$i] === '.') { @@ -166,7 +125,7 @@ private function reduceDots($uri) unset($parts[$i]); } } - return implode('/', $parts); + return '/'.implode('/', $parts); } /** @@ -188,6 +147,57 @@ private function dirname($path) return ''; } + public function getBaseSpec(): ?SpecObjectInterface + { + return $this->_baseSpec; + } + + public function getUri(): string + { + return $this->_uri; + } + + /** + * Resolve a relative URI to an absolute URI in the current context. + * @param string $uri + * @throws UnresolvableReferenceException + * @return string + */ + public function resolveRelativeUri(string $uri): string + { + $parts = parse_url($uri); + // absolute URI, no need to combine with baseURI + if (isset($parts['scheme'])) { + if (isset($parts['path'])) { + $parts['path'] = $this->reduceDots($parts['path']); + } + return $this->buildUri($parts); + } + + // convert absolute path on windows to a file:// URI. This is probably incomplete but should work with the majority of paths. + if (stripos(PHP_OS, 'WIN') === 0 && strncmp(substr($uri, 1), ':\\', 2) === 0) { + // convert absolute path on windows to a file:// URI. This is probably incomplete but should work with the majority of paths. + $absoluteUri = "file:///" . strtr($uri, [' ' => '%20', '\\' => '/']); + return $absoluteUri + . (isset($parts['fragment']) ? '#' . $parts['fragment'] : ''); + } + + $baseUri = $this->getUri(); + $baseParts = parse_url($baseUri); + if (isset($parts['path'][0]) && $parts['path'][0] === '/') { + // absolute path + $baseParts['path'] = $this->reduceDots($parts['path']); + } elseif (isset($parts['path'])) { + // relative path + $baseParts['path'] = $this->reduceDots(rtrim($this->dirname($baseParts['path'] ?? ''), '/') . '/' . $parts['path']); + } else { + throw new UnresolvableReferenceException("Invalid URI: '$uri'"); + } + $baseParts['query'] = $parts['query'] ?? null; + $baseParts['fragment'] = $parts['fragment'] ?? null; + return $this->buildUri($baseParts); + } + /** * Fetch referenced file by URI. * @@ -239,12 +249,12 @@ public function resolveReferenceData($uri, JsonPointer $pointer, $data, $toType) // transitive reference if (isset($referencedData['$ref'])) { - $subContext = new ReferenceContext(null, $uri, $this->_cache); - $subContext->mode = $this->mode; - return (new Reference($referencedData, $toType))->resolve($subContext); + /** @var Reference $referencedObject */ + return new Reference($referencedData, $toType); + } else { + /** @var SpecObjectInterface|array $referencedObject */ + $referencedObject = $toType !== null ? new $toType($referencedData) : $referencedData; } - /** @var SpecObjectInterface|array $referencedObject */ - $referencedObject = $toType !== null ? new $toType($referencedData) : $referencedData; $this->_cache->set($ref, $toType, $referencedObject); diff --git a/src/ReferenceContextCache.php b/src/ReferenceContextCache.php index 37ec4e35..74ed3028 100644 --- a/src/ReferenceContextCache.php +++ b/src/ReferenceContextCache.php @@ -13,10 +13,9 @@ class ReferenceContextCache { private $_cache = []; -// private $_isbase = []; - public function set($ref, $type, $data)//, $isbase = false) + public function set($ref, $type, $data) { $this->_cache[$ref][$type ?? ''] = $data; @@ -24,17 +23,8 @@ public function set($ref, $type, $data)//, $isbase = false) if ($type !== null && !isset($this->_cache[$ref][''])) { $this->_cache[$ref][''] = $data; } - -// if ($isbase) { -// $this->_isbase[$ref] = true; -// } } -// public function isBase($ref) -// { -// return $this->_isbase[$ref] ?? false; -// } - public function get($ref, $type) { return $this->_cache[$ref][$type ?? ''] ?? null; diff --git a/src/spec/PathItem.php b/src/spec/PathItem.php index 236bda81..f93e43fc 100644 --- a/src/spec/PathItem.php +++ b/src/spec/PathItem.php @@ -91,6 +91,12 @@ public function getSerializableData() if ($this->_ref instanceof Reference) { $data->{'$ref'} = $this->_ref->getReference(); } + if (isset($data->servers) && empty($data->servers)) { + unset($data->servers); + } + if (isset($data->parameters) && empty($data->parameters)) { + unset($data->parameters); + } return $data; } diff --git a/src/spec/Reference.php b/src/spec/Reference.php index b44b8fed..5f65a7e1 100644 --- a/src/spec/Reference.php +++ b/src/spec/Reference.php @@ -182,21 +182,7 @@ public function resolve(ReferenceContext $context = null) $referencedObject = $jsonReference->getJsonPointer()->evaluate($baseSpec); // transitive reference if ($referencedObject instanceof Reference) { - if ($referencedObject->_to === null) { - $referencedObject->_to = $this->_to; - } - $referencedObject->setContext($context); - - if ($referencedObject === $this) { // catch recursion - throw new UnresolvableReferenceException('Cyclic reference detected on a Reference Object.'); - } - - $transitiveRefResult = $referencedObject->resolve(); - - if ($transitiveRefResult === $this) { // catch recursion - throw new UnresolvableReferenceException('Cyclic reference detected on a Reference Object.'); - } - return $transitiveRefResult; + $referencedObject = $this->resolveTransitiveReference($referencedObject, $context); } if ($referencedObject instanceof SpecObjectInterface) { $referencedObject->setReferenceContext($context); @@ -211,8 +197,8 @@ public function resolve(ReferenceContext $context = null) // resolve in external document $file = $context->resolveRelativeUri($jsonReference->getDocumentUri()); -// echo $file . "\n"; try { + // TODO cache here? $referencedDocument = $context->fetchReferencedFile($file); } catch (\Throwable $e) { $exception = new UnresolvableReferenceException( @@ -223,31 +209,27 @@ public function resolve(ReferenceContext $context = null) $exception->context = $this->getDocumentPosition(); throw $exception; } - echo "\n\n" . $context->getUri() . "\n" . $file . "\n"; - echo json_encode($referencedDocument) . "\n"; - echo $context->mode; - $referencedDocument = $this->adjustRelativeReferences($referencedDocument, $file, null, $context); -// echo json_encode($referencedDocument, JSON_PRETTY_PRINT); + $referencedDocument = $this->adjustRelativeReferences($referencedDocument, $file, null, $context); $referencedObject = $context->resolveReferenceData($file, $jsonReference->getJsonPointer(), $referencedDocument, $this->_to); - if ($context->getUri() === $file) { - $newContext = $context; - } elseif ($jsonReference->getJsonPointer()->getPointer() === '') { - $newContext = new ReferenceContext($referencedObject instanceof SpecObjectInterface ? $referencedObject : null, $file, $context->getCache()); - if ($referencedObject instanceof DocumentContextInterface) { - $referencedObject->setDocumentContext($referencedObject, $jsonReference->getJsonPointer()); + if ($referencedObject instanceof DocumentContextInterface) { + if ($referencedObject->getDocumentPosition() === null && $this->getDocumentPosition() !== null) { + $referencedObject->setDocumentContext($context->getBaseSpec(), $this->getDocumentPosition()); } - } else { - // resolving references recursively does not work the same if we have not referenced - // 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, $context->getCache()); } - $newContext->throwException = $context->throwException; - $newContext->mode = $context->mode; - if ($referencedObject instanceof SpecObjectInterface) { - $referencedObject->setReferenceContext($newContext); + + // transitive reference + if ($referencedObject instanceof Reference) { + if ($context->mode === ReferenceContext::RESOLVE_MODE_INLINE && strncmp($referencedObject->getReference(), '#', 1) === 0) { + $referencedObject->setContext($context); + } else { + return $this->resolveTransitiveReference($referencedObject, $context); + } + } else { + if ($referencedObject instanceof SpecObjectInterface) { + $referencedObject->setReferenceContext($context); + } } return $referencedObject; @@ -272,6 +254,25 @@ public function resolve(ReferenceContext $context = null) } } + private function resolveTransitiveReference(Reference $referencedObject, ReferenceContext $context) + { + if ($referencedObject->_to === null) { + $referencedObject->_to = $this->_to; + } + $referencedObject->setContext($context); + + if ($referencedObject === $this) { // catch recursion + throw new UnresolvableReferenceException('Cyclic reference detected on a Reference Object.'); + } + + $transitiveRefResult = $referencedObject->resolve(); + + if ($transitiveRefResult === $this) { // catch recursion + throw new UnresolvableReferenceException('Cyclic reference detected on a Reference Object.'); + } + return $transitiveRefResult; + } + // adjust relative refernces inside of the file to match the context of the base file private function adjustRelativeReferences($referencedDocument, $basePath, $baseDocument = null, $oContext = null) { @@ -283,23 +284,15 @@ private function adjustRelativeReferences($referencedDocument, $basePath, $baseD foreach($referencedDocument as $key => $value) { if ($key === '$ref' && is_string($value)) { if (isset($value[0]) && $value[0] === '#') { - echo "resolbin....\n"; -// if ($oContext === null || $oContext->mode !== ReferenceContext::RESOLVE_MODE_INLINE) { - // direcly inline references in the same document, - // these are not going to be valid in the new context anymore - $referencedDocument = (new JsonPointer(substr($value, 1)))->evaluate($baseDocument); - break; -// } + // direcly inline references in the same document, + // these are not going to be valid in the new context anymore + return (new JsonPointer(substr($value, 1)))->evaluate($baseDocument); } -// echo "\n\n$value\n"; $referencedDocument[$key] = $context->resolveRelativeUri($value); $parts = explode('#', $referencedDocument[$key], 2); if ($parts[0] === $oContext->getUri()) { $referencedDocument[$key] = '#' . ($parts[1] ?? ''); } - echo $referencedDocument[$key]; -// echo "$referencedDocument[$key]\n"; - continue; } if (is_array($value)) { diff --git a/tests/ReferenceContextTest.php b/tests/ReferenceContextTest.php index 73014fc8..92d9d93e 100644 --- a/tests/ReferenceContextTest.php +++ b/tests/ReferenceContextTest.php @@ -6,7 +6,7 @@ class ReferenceContextTest extends \PHPUnit\Framework\TestCase { - public function uriProvider() + public function resolveUriProvider() { $data = [ [ @@ -14,37 +14,77 @@ public function uriProvider() 'definitions.yaml', // referenced URI 'https://example.com/definitions.yaml', // expected result ], + [ + 'https://example.com/openapi.yaml', // base URI + 'definitions.yaml#/components/Pet', // referenced URI + 'https://example.com/definitions.yaml#/components/Pet', // expected result + ], + [ 'https://example.com/openapi.yaml', // base URI '/definitions.yaml', // referenced URI 'https://example.com/definitions.yaml', // expected result ], + [ + 'https://example.com/openapi.yaml', // base URI + '/definitions.yaml#/components/Pet', // referenced URI + 'https://example.com/definitions.yaml#/components/Pet', // expected result + ], + [ 'https://example.com/api/openapi.yaml', // base URI 'definitions.yaml', // referenced URI 'https://example.com/api/definitions.yaml', // expected result ], + [ + 'https://example.com/api/openapi.yaml', // base URI + 'definitions.yaml#/components/Pet', // referenced URI + 'https://example.com/api/definitions.yaml#/components/Pet', // expected result + ], + [ 'https://example.com/api/openapi.yaml', // base URI '/definitions.yaml', // referenced URI 'https://example.com/definitions.yaml', // expected result ], + [ + 'https://example.com/api/openapi.yaml', // base URI + '/definitions.yaml#/components/Pet', // referenced URI + 'https://example.com/definitions.yaml#/components/Pet', // expected result + ], + [ 'https://example.com/api/openapi.yaml', // base URI '../definitions.yaml', // referenced URI 'https://example.com/definitions.yaml', // expected result ], + [ + 'https://example.com/api/openapi.yaml', // base URI + '../definitions.yaml#/components/Pet', // referenced URI + 'https://example.com/definitions.yaml#/components/Pet', // expected result + ], + [ '/var/www/openapi.yaml', // base URI 'definitions.yaml', // referenced URI 'file:///var/www/definitions.yaml', // expected result ], + [ + '/var/www/openapi.yaml', // base URI + 'definitions.yaml#/components/Pet', // referenced URI + 'file:///var/www/definitions.yaml#/components/Pet', // expected result + ], + [ '/var/www/openapi.yaml', // base URI '/var/definitions.yaml', // referenced URI 'file:///var/definitions.yaml', // expected result ], - + [ + '/var/www/openapi.yaml', // base URI + '/var/definitions.yaml#/components/Pet', // referenced URI + 'file:///var/definitions.yaml#/components/Pet', // expected result + ], ]; // absolute URLs should not be changed @@ -54,18 +94,29 @@ public function uriProvider() 'file:///var/www/definitions.yaml', 'file:///var/www/definitions.yaml', ]; + $data[] = [ + $url, + 'file:///var/www/definitions.yaml#/components/Pet', + 'file:///var/www/definitions.yaml#/components/Pet', + ]; + $data[] = [ $url, 'https://example.com/definitions.yaml', 'https://example.com/definitions.yaml', ]; + $data[] = [ + $url, + 'https://example.com/definitions.yaml#/components/Pet', + 'https://example.com/definitions.yaml#/components/Pet', + ]; } return $data; } /** - * @dataProvider uriProvider + * @dataProvider resolveUriProvider */ public function testResolveUri($baseUri, $referencedUri, $expected) { @@ -73,4 +124,77 @@ public function testResolveUri($baseUri, $referencedUri, $expected) $this->assertEquals($expected, $context->resolveRelativeUri($referencedUri)); } + public function normalizeUriProvider() + { + $data = [ + [ + 'https://example.com/openapi.yaml', + 'https://example.com/openapi.yaml', + ], + [ + 'https://example.com/openapi.yaml#/components/Pet', + 'https://example.com/openapi.yaml#/components/Pet', + ], + [ + 'https://example.com/./openapi.yaml', + 'https://example.com/openapi.yaml', + ], + [ + 'https://example.com/./openapi.yaml#/components/Pet', + 'https://example.com/openapi.yaml#/components/Pet', + ], + [ + 'https://example.com/api/../openapi.yaml', + 'https://example.com/openapi.yaml', + ], + [ + 'https://example.com/api/../openapi.yaml#/components/Pet', + 'https://example.com/openapi.yaml#/components/Pet', + ], + [ + 'https://example.com/../openapi.yaml', + 'https://example.com/../openapi.yaml', + ], + [ + 'https://example.com/../openapi.yaml#/components/Pet', + 'https://example.com/../openapi.yaml#/components/Pet', + ], + [ + '/definitions.yaml', + 'file:///definitions.yaml', + ], + [ + '/definitions.yaml#/components/Pet', + 'file:///definitions.yaml#/components/Pet', + ], + [ + '/var/www/definitions.yaml', + 'file:///var/www/definitions.yaml', + ], + [ + '/var/www/definitions.yaml#/components/Pet', + 'file:///var/www/definitions.yaml#/components/Pet', + ], + [ + '/var/www/api/../definitions.yaml', + 'file:///var/www/definitions.yaml', + ], + [ + '/var/www/api/../definitions.yaml#/components/Pet', + 'file:///var/www/definitions.yaml#/components/Pet', + ], + ]; + + return $data; + } + + /** + * @dataProvider normalizeUriProvider + */ + public function testNormalizeUri($uri, $expected) + { + $context = new ReferenceContext(null, $uri); + $this->assertEquals($expected, $context->getUri()); + } + } diff --git a/tests/spec/PathTest.php b/tests/spec/PathTest.php index 8368f784..cf50b19d 100644 --- a/tests/spec/PathTest.php +++ b/tests/spec/PathTest.php @@ -161,9 +161,13 @@ public function testPathItemReference() $this->assertInstanceOf(Operation::class, $ReferencedBarPath->get); $this->assertEquals('getBar', $ReferencedBarPath->get->operationId); - $this->assertInstanceOf(Reference::class, $ReferencedBarPath->get->responses['200']); + $this->assertInstanceOf(Reference::class, $reference200 = $ReferencedBarPath->get->responses['200']); $this->assertInstanceOf(Response::class, $ReferencedBarPath->get->responses['404']); - $this->assertEquals('non-existing resource', $barPath->get->responses['404']->description); + $this->assertEquals('non-existing resource', $ReferencedBarPath->get->responses['404']->description); + + $path200 = $reference200->resolve(); + $this->assertInstanceOf(Response::class, $path200); + $this->assertEquals('A bar', $path200->description); /** @var $openapi OpenApi */ $openapi = Reader::readFromYamlFile($file, \cebe\openapi\spec\OpenApi::class, true); diff --git a/tests/spec/ReferenceTest.php b/tests/spec/ReferenceTest.php index 56150d57..29928b4c 100644 --- a/tests/spec/ReferenceTest.php +++ b/tests/spec/ReferenceTest.php @@ -417,7 +417,35 @@ public function testTransitiveReferenceCyclic() $openapi->resolveReferences(new \cebe\openapi\ReferenceContext($openapi, 'file:///tmp/openapi.yaml')); } - public function testResolveRelativePath() + public function testTransitiveReferenceOverTwoFiles() + { + $openapi = Reader::readFromYamlFile(__DIR__ . '/data/reference/structure.yaml', OpenApi::class, \cebe\openapi\ReferenceContext::RESOLVE_MODE_INLINE); + + $yaml = \cebe\openapi\Writer::writeToYaml($openapi); + + $this->assertEquals( +<<assertEquals( +<< Date: Fri, 10 Jul 2020 21:32:30 +0200 Subject: [PATCH 06/39] cleanup --- src/spec/Reference.php | 1 - tests/spec/data/reference/structure/paths.yml | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/spec/Reference.php b/src/spec/Reference.php index 5f65a7e1..c604699f 100644 --- a/src/spec/Reference.php +++ b/src/spec/Reference.php @@ -58,7 +58,6 @@ public function __construct(array $data, string $to = null) ); } if (!is_string($data['$ref'])) { - print_r($data); throw new TypeErrorException( 'Unable to instantiate Reference Object, value of $ref must be a string.' ); diff --git a/tests/spec/data/reference/structure/paths.yml b/tests/spec/data/reference/structure/paths.yml index 636af21e..46f4d6b3 100644 --- a/tests/spec/data/reference/structure/paths.yml +++ b/tests/spec/data/reference/structure/paths.yml @@ -2,4 +2,4 @@ get: $ref: 'paths/pet.yml#/get' /cat: - $ref: 'paths/cat.yml' \ No newline at end of file + $ref: 'paths/cat.yml' From 3d356d3e694d99434319057f2f9d35fad9ba3d9f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 9 Aug 2020 19:24:01 +0200 Subject: [PATCH 07/39] Bump prismjs from 1.17.1 to 1.21.0 (#76) Bumps [prismjs](https://github.com/PrismJS/prism) from 1.17.1 to 1.21.0. - [Release notes](https://github.com/PrismJS/prism/releases) - [Changelog](https://github.com/PrismJS/prism/blob/master/CHANGELOG.md) - [Commits](https://github.com/PrismJS/prism/compare/v1.17.1...v1.21.0) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- yarn.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/yarn.lock b/yarn.lock index d5d682a4..a6dd9b25 100644 --- a/yarn.lock +++ b/yarn.lock @@ -164,9 +164,9 @@ classnames@^2.2.0, classnames@^2.2.3, classnames@^2.2.6: integrity sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q== clipboard@^2.0.0: - version "2.0.4" - resolved "https://registry.yarnpkg.com/clipboard/-/clipboard-2.0.4.tgz#836dafd66cf0fea5d71ce5d5b0bf6e958009112d" - integrity sha512-Vw26VSLRpJfBofiVaFb/I8PVfdI1OxKcYShe6fm0sP/DtmiWQNCjhM/okTvdCo0G+lMMm1rMYbk4IK4x1X+kgQ== + version "2.0.6" + resolved "https://registry.yarnpkg.com/clipboard/-/clipboard-2.0.6.tgz#52921296eec0fdf77ead1749421b21c968647376" + integrity sha512-g5zbiixBRk/wyKakSwCKd7vQXDjFnAMGHoEyBogG/bw9kTD9GvdAvaoRR1ALcEzt3pVKxZR0pViekPMIS0QyGg== dependencies: good-listener "^1.2.2" select "^1.1.2" @@ -1026,9 +1026,9 @@ polished@^3.0.3: "@babel/runtime" "^7.4.5" prismjs@^1.15.0: - version "1.17.1" - resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.17.1.tgz#e669fcbd4cdd873c35102881c33b14d0d68519be" - integrity sha512-PrEDJAFdUGbOP6xK/UsfkC5ghJsPJviKgnQOoxaDbBjwc8op68Quupwt1DeAFoG8GImPhiKXAvvsH7wDSLsu1Q== + version "1.21.0" + resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.21.0.tgz#36c086ec36b45319ec4218ee164c110f9fc015a3" + integrity sha512-uGdSIu1nk3kej2iZsLyDoJ7e9bnPzIgY0naW/HdknGj61zScaprVEVGHrPoXqI+M9sP0NDnTK2jpkvmldpuqDw== optionalDependencies: clipboard "^2.0.0" From 9796b0e0859c5284bd48b9357ee679fa6f422114 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 15 Sep 2020 14:55:21 +0200 Subject: [PATCH 08/39] Bump node-fetch from 2.6.0 to 2.6.1 (#78) Bumps [node-fetch](https://github.com/bitinn/node-fetch) from 2.6.0 to 2.6.1. - [Release notes](https://github.com/bitinn/node-fetch/releases) - [Changelog](https://github.com/node-fetch/node-fetch/blob/master/docs/CHANGELOG.md) - [Commits](https://github.com/bitinn/node-fetch/compare/v2.6.0...v2.6.1) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index a6dd9b25..88489d43 100644 --- a/yarn.lock +++ b/yarn.lock @@ -840,9 +840,9 @@ node-fetch-h2@^2.3.0: http2-client "^1.2.5" node-fetch@^2.3.0: - version "2.6.0" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.0.tgz#e633456386d4aa55863f676a7ab0daa8fdecb0fd" - integrity sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA== + version "2.6.1" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" + integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw== node-readfiles@^0.2.0: version "0.2.0" From d8e9b9344679614225d7a3c8b5c8e6de3745d79e Mon Sep 17 00:00:00 2001 From: Marcel Thole Date: Thu, 29 Oct 2020 08:01:00 +0100 Subject: [PATCH 09/39] Bump yaml version --- .travis.yml | 8 ++++---- composer.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index e3afb510..81827dfb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,21 +4,21 @@ matrix: include: # linux tests - php: '7.1' - env: YAML=^3.0 + env: YAML=^3.4 - php: '7.1' env: YAML=~4.3.0 - php: '7.2' - env: YAML=^3.0 + env: YAML=^3.4 - php: '7.2' env: YAML=~4.3.0 - php: '7.3' - env: YAML=^3.0 + env: YAML=^3.4 - php: '7.3' env: YAML=~4.3.0 - php: '7.3' env: YAML=^5.0 - php: '7.4' - env: YAML=^3.0 + env: YAML=^3.4 - php: '7.4' env: YAML=~4.3.0 - php: '7.4' diff --git a/composer.json b/composer.json index 1e05bd54..457bc832 100644 --- a/composer.json +++ b/composer.json @@ -20,7 +20,7 @@ "require": { "php": ">=7.1.0", "ext-json": "*", - "symfony/yaml": "^3.0 | ^4.0 | ^5.0", + "symfony/yaml": "^3.4 | ^4.0 | ^5.0", "justinrainbow/json-schema": "^5.0" }, "require-dev": { From 93387417f04d82b5100f2c85e1835e01077f8ebd Mon Sep 17 00:00:00 2001 From: Marcel Thole Date: Thu, 29 Oct 2020 17:01:47 +0100 Subject: [PATCH 10/39] Fix travis build by allow composer2 file structue --- tests/ReaderTest.php | 6 +++++- tests/spec/OpenApiTest.php | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/tests/ReaderTest.php b/tests/ReaderTest.php index 6aa1faed..d6d9e87f 100644 --- a/tests/ReaderTest.php +++ b/tests/ReaderTest.php @@ -96,8 +96,12 @@ public function testSymfonyYamlBugHunt() { // skip test on symfony/yaml 5.0 due to bug https://github.com/symfony/symfony/issues/34805 $installed = json_decode(file_get_contents(__DIR__ . '/../vendor/composer/installed.json'), true); + // Check for composer 2.0 structure + if (array_key_exists('packages', $installed)) { + $installed = $installed['packages']; + } foreach($installed as $pkg) { - if ($pkg['name'] === 'symfony/yaml' && strncmp($pkg['version_normalized'], '5.0', 3) === 0) { + if ($pkg['name'] === 'symfony/yaml' && version_compare($pkg['version'], 'v4.4', '>=')) { $this->markTestSkipped( 'This test is incompatible with symfony/yaml 4.4 and 5.0, see symfony bug https://github.com/symfony/symfony/issues/34805' ); diff --git a/tests/spec/OpenApiTest.php b/tests/spec/OpenApiTest.php index dc9dd449..2187e2e4 100644 --- a/tests/spec/OpenApiTest.php +++ b/tests/spec/OpenApiTest.php @@ -186,8 +186,12 @@ public function testSpecs($openApiFile) // skip test on symfony/yaml 5.0 due to bug https://github.com/symfony/symfony/issues/34805 if ($openApiFile === 'oai/openapi-specification/examples/v3.0/uspto.yaml') { $installed = json_decode(file_get_contents(__DIR__ . '/../../vendor/composer/installed.json'), true); + // Check for composer 2.0 structure + if (array_key_exists('packages', $installed)) { + $installed = $installed['packages']; + } foreach ($installed as $pkg) { - if ($pkg['name'] === 'symfony/yaml' && strncmp($pkg['version_normalized'], '5.0', 3) === 0) { + if ($pkg['name'] === 'symfony/yaml' && version_compare($pkg['version'], 'v4.4', '>=')) { $this->markTestSkipped( 'This test is incompatible with symfony/yaml 4.4 and 5.0, see symfony bug https://github.com/symfony/symfony/issues/34805' ); From 8e9782b79e7edb510e51a3134af25bf30a220c83 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Mon, 23 Nov 2020 17:58:22 +0100 Subject: [PATCH 11/39] Update README.md added @dsuurlant / response2schema --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index ae7a86e5..3e6ec4b5 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,7 @@ do awesome work: - [cebe/yii2-openapi](https://github.com/cebe/yii2-openapi) Code Generator for REST API from OpenAPI 3 Descriptions, includes fake data generator. - [cebe/yii2-app-api](https://github.com/cebe/yii2-app-api) Yii framework application template for developing API-first applications. - [league/openapi-psr7-validator](https://github.com/thephpleague/openapi-psr7-validator) validates PSR-7 messages (HTTP request/response) against OpenAPI descriptions. +- [dsuurlant/response2schema](https://github.com/dsuurlant/response2schema) a quick and easy tool for generating OpenAPI schemas based on example data. - ... ([add yours](https://github.com/cebe/php-openapi/edit/master/README.md#L24)) ## Usage From dac88708214c7f26909b790cc963865d4d836b3c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Dec 2020 13:43:49 +0100 Subject: [PATCH 12/39] Bump ini from 1.3.5 to 1.3.7 (#86) Bumps [ini](https://github.com/isaacs/ini) from 1.3.5 to 1.3.7. - [Release notes](https://github.com/isaacs/ini/releases) - [Commits](https://github.com/isaacs/ini/compare/v1.3.5...v1.3.7) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 88489d43..b829c619 100644 --- a/yarn.lock +++ b/yarn.lock @@ -557,9 +557,9 @@ inherits@2.0.4: integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== ini@^1.3.0: - version "1.3.5" - resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" - integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw== + version "1.3.7" + resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.7.tgz#a09363e1911972ea16d7a8851005d84cf09a9a84" + integrity sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ== invert-kv@^1.0.0: version "1.0.0" From 76b1077e62da6dc1c4cc0b84a7bd171b90d82c7f Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Mon, 14 Dec 2020 23:23:01 +0100 Subject: [PATCH 13/39] Cache parsed file content --- src/ReferenceContext.php | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/ReferenceContext.php b/src/ReferenceContext.php index e78d3fdc..a5f02d25 100644 --- a/src/ReferenceContext.php +++ b/src/ReferenceContext.php @@ -208,6 +208,10 @@ public function resolveRelativeUri(string $uri): string */ public function fetchReferencedFile($uri) { + if ($this->_cache->has('FILE_CONTENT://' . $uri, 'FILE_CONTENT')) { + return $this->_cache->get('FILE_CONTENT://' . $uri, 'FILE_CONTENT'); + } + $content = file_get_contents($uri); if ($content === false) { $e = new IOException("Failed to read file: '$uri'"); @@ -216,10 +220,12 @@ public function fetchReferencedFile($uri) } // TODO lazy content detection, should be improved if (strpos(ltrim($content), '{') === 0) { - return json_decode($content, true); + $parsedContent = json_decode($content, true); } else { - return Yaml::parse($content); + $parsedContent = Yaml::parse($content); } + $this->_cache->set('FILE_CONTENT://' . $uri, 'FILE_CONTENT', $parsedContent); + return $parsedContent; } /** From 10a51356cef42c43a65f4e0f1b07968f5352017b Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Mon, 14 Dec 2020 23:36:33 +0100 Subject: [PATCH 14/39] Apply suggestions from code review --- src/spec/Reference.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/spec/Reference.php b/src/spec/Reference.php index c604699f..a1e2b859 100644 --- a/src/spec/Reference.php +++ b/src/spec/Reference.php @@ -197,7 +197,6 @@ public function resolve(ReferenceContext $context = null) // resolve in external document $file = $context->resolveRelativeUri($jsonReference->getDocumentUri()); try { - // TODO cache here? $referencedDocument = $context->fetchReferencedFile($file); } catch (\Throwable $e) { $exception = new UnresolvableReferenceException( From 333aa5f44b0baeb94502cdcfa0053a07efc55533 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Mon, 14 Dec 2020 23:41:36 +0100 Subject: [PATCH 15/39] Apply suggestions from code review --- src/Reader.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Reader.php b/src/Reader.php index b52ebe13..7dc00a92 100644 --- a/src/Reader.php +++ b/src/Reader.php @@ -62,7 +62,8 @@ public static function readFromYaml(string $yaml, string $baseType = OpenApi::cl * If `true`, all [[Reference]] objects will be replaced with their referenced spec objects by calling * [[SpecObjectInterface::resolveReferences()]]. * Since version 1.5.0 this can be a string indicating the reference resolving mode: - * - TODO inline vs all + * - `inline` only resolve references to external files. + * - `all` resolve all references exceot recursive references. * @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. @@ -102,7 +103,8 @@ public static function readFromJsonFile(string $fileName, string $baseType = Ope * If `true`, all [[Reference]] objects will be replaced with their referenced spec objects by calling * [[SpecObjectInterface::resolveReferences()]]. * Since version 1.5.0 this can be a string indicating the reference resolving mode: - * - TODO inline vs all + * - `inline` only resolve references to external files. + * - `all` resolve all references exceot recursive references. * @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. From 34f6e203e2fc16e570dd280487d42e1296c0e4d2 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Mon, 14 Dec 2020 23:46:33 +0100 Subject: [PATCH 16/39] Update Reader.php fix typo --- src/Reader.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Reader.php b/src/Reader.php index 7dc00a92..583fc725 100644 --- a/src/Reader.php +++ b/src/Reader.php @@ -63,7 +63,7 @@ public static function readFromYaml(string $yaml, string $baseType = OpenApi::cl * [[SpecObjectInterface::resolveReferences()]]. * Since version 1.5.0 this can be a string indicating the reference resolving mode: * - `inline` only resolve references to external files. - * - `all` resolve all references exceot recursive references. + * - `all` resolve all references except recursive references. * @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. @@ -104,7 +104,7 @@ public static function readFromJsonFile(string $fileName, string $baseType = Ope * [[SpecObjectInterface::resolveReferences()]]. * Since version 1.5.0 this can be a string indicating the reference resolving mode: * - `inline` only resolve references to external files. - * - `all` resolve all references exceot recursive references. + * - `all` resolve all references except recursive references. * @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. From 696bfd0902da5727117abebb65fdd96ae61ab667 Mon Sep 17 00:00:00 2001 From: Can Vural Date: Thu, 26 Nov 2020 01:09:09 +0100 Subject: [PATCH 17/39] PHP8 support --- .gitignore | 2 ++ .travis.yml | 4 ++-- Makefile | 6 ++++-- composer.json | 3 +-- tests/ReaderTest.php | 8 +++++++- tests/spec/MediaTypeTest.php | 8 +++++++- tests/spec/OpenApiTest.php | 7 ++++++- 7 files changed, 29 insertions(+), 9 deletions(-) diff --git a/.gitignore b/.gitignore index 9356be31..fe462e09 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,5 @@ /node_modules /.php_cs.cache + +php-cs-fixer.phar diff --git a/.travis.yml b/.travis.yml index 81827dfb..ee9e4f98 100644 --- a/.travis.yml +++ b/.travis.yml @@ -24,7 +24,7 @@ matrix: - php: '7.4' env: YAML=^5.0 - php: nightly - env: YAML=~4.3.0 + env: YAML=~4.4.0 # windows tests # https://travis-ci.community/t/where-to-contribute-php-support-for-windows/304 - os: windows @@ -57,6 +57,6 @@ script: - make lint - make stan - make test - - if [[ $TRAVIS_PHP_VERSION = "7.3" || $TRAVIS_PHP_VERSION = "nightly" ]]; then true; else make check-style; fi + - make check-style - make coverage diff --git a/Makefile b/Makefile index 5305d1b5..5549cd51 100644 --- a/Makefile +++ b/Makefile @@ -8,8 +8,8 @@ endif all: -check-style: - vendor/bin/php-cs-fixer fix src/ --diff --dry-run +check-style: php-cs-fixer.phar + PHP_CS_FIXER_IGNORE_ENV=1 ./php-cs-fixer.phar fix src/ --diff --dry-run fix-style: vendor/bin/indent --tabs composer.json @@ -42,6 +42,8 @@ schemas/openapi-v3.0.json: vendor/oai/openapi-specification/schemas/v3.0/schema. schemas/openapi-v3.0.yaml: vendor/oai/openapi-specification/schemas/v3.0/schema.yaml cp $< $@ +php-cs-fixer.phar: + wget https://github.com/FriendsOfPHP/PHP-CS-Fixer/releases/download/v2.16.7/php-cs-fixer.phar && chmod +x php-cs-fixer.phar # find spec classes that are not mentioned in tests with @covers yet coverage: .php-openapi-covA .php-openapi-covB diff --git a/composer.json b/composer.json index d6201b0b..81b89d0c 100644 --- a/composer.json +++ b/composer.json @@ -25,8 +25,7 @@ }, "require-dev": { "cebe/indent": "*", - "friendsofphp/php-cs-fixer": "~2.16.1", - "phpunit/phpunit": "^6.5", + "phpunit/phpunit": "^6.5 || ^7.5 || ^8.5 || ^9.4", "oai/openapi-specification": "3.0.2", "mermade/openapi3-examples": "1.0.0", diff --git a/tests/ReaderTest.php b/tests/ReaderTest.php index d6d9e87f..6e961ab6 100644 --- a/tests/ReaderTest.php +++ b/tests/ReaderTest.php @@ -112,7 +112,13 @@ public function testSymfonyYamlBugHunt() $openapi = \cebe\openapi\Reader::readFromYamlFile($openApiFile); $inlineYamlExample = $openapi->paths['/']->get->responses['200']->content['application/json']->example; - $this->assertInternalType('array', $inlineYamlExample); + + if (method_exists($this, 'assertIsArray')) { + $this->assertIsArray($inlineYamlExample); + } else { + $this->assertInternalType('array', $inlineYamlExample); + } + $expectedArray = json_decode(<<assertTrue($result); $this->assertInstanceOf(Reference::class, $mediaType->schema); - $this->assertInternalType('array', $mediaType->examples); + + if (method_exists($this, 'assertIsArray')) { + $this->assertIsArray($mediaType->examples); + } else { + $this->assertInternalType('array', $mediaType->examples); + } + $this->assertCount(3, $mediaType->examples); $this->assertArrayHasKey('cat', $mediaType->examples); $this->assertArrayHasKey('dog', $mediaType->examples); diff --git a/tests/spec/OpenApiTest.php b/tests/spec/OpenApiTest.php index 2187e2e4..bf321f1b 100644 --- a/tests/spec/OpenApiTest.php +++ b/tests/spec/OpenApiTest.php @@ -52,7 +52,12 @@ public function testReadPetStore() // servers - $this->assertInternalType('array', $openapi->servers); + if (method_exists($this, 'assertIsArray')) { + $this->assertIsArray($openapi->servers); + } else { + $this->assertInternalType('array', $openapi->servers); + } + $this->assertCount(1, $openapi->servers); foreach ($openapi->servers as $server) { $this->assertInstanceOf(\cebe\openapi\spec\Server::class, $server); From 1be17d69adacce2fa6229f155fa7897de2fa21a7 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Tue, 15 Dec 2020 21:01:05 +0100 Subject: [PATCH 18/39] improved types for phpstan --- src/Reader.php | 8 ++++++-- src/ReferenceContext.php | 3 +-- src/spec/Callback.php | 17 +++++++++++++++-- src/spec/Paths.php | 11 +++++++++-- src/spec/Reference.php | 23 +++++++++++++++++++++-- 5 files changed, 52 insertions(+), 10 deletions(-) diff --git a/src/Reader.php b/src/Reader.php index 583fc725..d073fa01 100644 --- a/src/Reader.php +++ b/src/Reader.php @@ -85,7 +85,9 @@ public static function readFromJsonFile(string $fileName, string $baseType = Ope if (is_string($resolveReferences)) { $context->mode = $resolveReferences; } - $spec->setDocumentContext($spec, new JsonPointer('')); + if ($spec instanceof DocumentContextInterface) { + $spec->setDocumentContext($spec, new JsonPointer('')); + } $spec->resolveReferences(); } return $spec; @@ -126,7 +128,9 @@ public static function readFromYamlFile(string $fileName, string $baseType = Ope if (is_string($resolveReferences)) { $context->mode = $resolveReferences; } - $spec->setDocumentContext($spec, new JsonPointer('')); + if ($spec instanceof DocumentContextInterface) { + $spec->setDocumentContext($spec, new JsonPointer('')); + } $spec->resolveReferences(); } return $spec; diff --git a/src/ReferenceContext.php b/src/ReferenceContext.php index a5f02d25..c379e7c1 100644 --- a/src/ReferenceContext.php +++ b/src/ReferenceContext.php @@ -238,7 +238,7 @@ public function fetchReferencedFile($uri) * @param JsonPointer $pointer * @param array $data * @param string|null $toType - * @return SpecObjectInterface|array + * @return SpecObjectInterface|array|null */ public function resolveReferenceData($uri, JsonPointer $pointer, $data, $toType) { @@ -255,7 +255,6 @@ public function resolveReferenceData($uri, JsonPointer $pointer, $data, $toType) // transitive reference if (isset($referencedData['$ref'])) { - /** @var Reference $referencedObject */ return new Reference($referencedData, $toType); } else { /** @var SpecObjectInterface|array $referencedObject */ diff --git a/src/spec/Callback.php b/src/spec/Callback.php index b73ae8fe..06bbd58a 100644 --- a/src/spec/Callback.php +++ b/src/spec/Callback.php @@ -22,12 +22,25 @@ */ class Callback implements SpecObjectInterface, DocumentContextInterface { + /** + * @var string|null + */ private $_url; + /** + * @var PathItem + */ private $_pathItem; - + /** + * @var array + */ private $_errors = []; - + /** + * @var SpecObjectInterface|null + */ private $_baseDocument; + /** + * @var JsonPointer|null + */ private $_jsonPointer; diff --git a/src/spec/Paths.php b/src/spec/Paths.php index e87606a0..a2da8a10 100644 --- a/src/spec/Paths.php +++ b/src/spec/Paths.php @@ -34,10 +34,17 @@ class Paths implements SpecObjectInterface, DocumentContextInterface, ArrayAcces * @var (PathItem|null)[] */ private $_paths = []; - + /** + * @var array + */ private $_errors = []; - + /** + * @var SpecObjectInterface|null + */ private $_baseDocument; + /** + * @var JsonPointer|null + */ private $_jsonPointer; diff --git a/src/spec/Reference.php b/src/spec/Reference.php index a1e2b859..49f735d2 100644 --- a/src/spec/Reference.php +++ b/src/spec/Reference.php @@ -29,14 +29,33 @@ */ class Reference implements SpecObjectInterface, DocumentContextInterface { + /** + * @var string + */ private $_to; + /** + * @var string + */ private $_ref; + /** + * @var JsonReference|null + */ private $_jsonReference; + /** + * @var ReferenceContext + */ private $_context; - + /** + * @var SpecObjectInterface|null + */ private $_baseDocument; + /** + * @var JsonPointer|null + */ private $_jsonPointer; - + /** + * @var array + */ private $_errors = []; /** From 63e19e164f2e657fc08207824fbb65a190b52d45 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Tue, 15 Dec 2020 21:23:13 +0100 Subject: [PATCH 19/39] Create php.yml --- .github/workflows/php.yml | 48 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 .github/workflows/php.yml diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml new file mode 100644 index 00000000..ee278b52 --- /dev/null +++ b/.github/workflows/php.yml @@ -0,0 +1,48 @@ +name: PHP Composer + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Validate composer.json and composer.lock + run: composer validate + + - name: Cache Composer packages + id: composer-cache + uses: actions/cache@v2 + with: + path: vendor + key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }} + restore-keys: | + ${{ runner.os }}-php- + + - name: Install dependencies + if: steps.composer-cache.outputs.cache-hit != 'true' + run: | + make install + composer require symfony/yaml:"${YAML}" --prefer-dist --no-interaction + + - name: Validate test data + run: make lint + + - name: PHP Stan analysis + run: make stan + + - name: PHPUnit tests + run: make test + + - name: Check code style + run: make check-style + + - name: Code coverage + run: make coverage From 16061438c756852dd7d77a8a5b39da7a409b9fbb Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Tue, 15 Dec 2020 21:33:21 +0100 Subject: [PATCH 20/39] Update php.yml --- .github/workflows/php.yml | 96 +++++++++++++++++++++++++-------------- 1 file changed, 61 insertions(+), 35 deletions(-) diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index ee278b52..41c5995b 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -9,40 +9,66 @@ on: jobs: build: - runs-on: ubuntu-latest + strategy: + fail-fast: true + matrix: +# os: [ubuntu-latest, macos-latest, windows-latest] + os: [ubuntu-latest] + php: ['7.1', '7.2', '7.3', '7.4', '8.0'] + yaml: ['^5.0', '^4.3', '^3.4'] + + runs-on: ${{ matrix.os }} + env: + YAML: ${{ matrix.yaml }} steps: - - uses: actions/checkout@v2 - - - name: Validate composer.json and composer.lock - run: composer validate - - - name: Cache Composer packages - id: composer-cache - uses: actions/cache@v2 - with: - path: vendor - key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }} - restore-keys: | - ${{ runner.os }}-php- - - - name: Install dependencies - if: steps.composer-cache.outputs.cache-hit != 'true' - run: | - make install - composer require symfony/yaml:"${YAML}" --prefer-dist --no-interaction - - - name: Validate test data - run: make lint - - - name: PHP Stan analysis - run: make stan - - - name: PHPUnit tests - run: make test - - - name: Check code style - run: make check-style - - - name: Code coverage - run: make coverage + - uses: actions/checkout@v2 + + - name: Install PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + ini-values: date.timezone='UTC' + coverage: pcov + tools: composer:v2 + + - name: Determine composer cache directory on Linux + if: matrix.os == 'ubuntu-latest' + run: echo "COMPOSER_CACHE_DIR=$(composer config cache-dir)" >> $GITHUB_ENV + + - name: Determine composer cache directory on Windows + if: matrix.os == 'windows-latest' + run: echo "COMPOSER_CACHE_DIR=~\AppData\Local\Composer" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append + + - name: Cache dependencies installed with composer + uses: actions/cache@v2 + with: + path: ${{ env.COMPOSER_CACHE_DIR }} + key: php${{ matrix.php }}-composer-${{ hashFiles('**/composer.json') }} + restore-keys: | + php${{ matrix.php }}-composer- + + + - name: Validate composer.json and composer.lock + run: composer validate + + - name: Install dependencies + if: steps.composer-cache.outputs.cache-hit != 'true' + run: | + make install + composer require symfony/yaml:"${YAML}" --prefer-dist --no-interaction --ansi + + - name: Validate test data + run: make lint + + - name: PHP Stan analysis + run: make stan + + - name: PHPUnit tests + run: make test + + - name: Check code style + run: make check-style + + - name: Code coverage + run: make coverage From fa7ec08fd892d968b0d49eb88aeb1a1a227fb2cc Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Tue, 15 Dec 2020 21:43:34 +0100 Subject: [PATCH 21/39] improve composer install --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 5305d1b5..86d9b187 100644 --- a/Makefile +++ b/Makefile @@ -17,7 +17,7 @@ fix-style: vendor/bin/php-cs-fixer fix src/ --diff install: - composer install --prefer-dist --no-interaction + composer install --prefer-dist --no-interaction --no-progress --no-suggest yarn install test: From eb1cc3f75eb6f1df9827415144474778d64c96ac Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Tue, 15 Dec 2020 21:53:17 +0100 Subject: [PATCH 22/39] Update php.yml --- .github/workflows/php.yml | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index 41c5995b..f990eaf6 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -10,13 +10,25 @@ jobs: build: strategy: - fail-fast: true + fail-fast: false matrix: # os: [ubuntu-latest, macos-latest, windows-latest] os: [ubuntu-latest] php: ['7.1', '7.2', '7.3', '7.4', '8.0'] yaml: ['^5.0', '^4.3', '^3.4'] - + exclude: + # Symfony YAML does not run on PHP 7.1 + - php: '7.1' + yaml: '^5.0' + include: + - php: '7.4' + os: windows-latest + yaml: '^5.0' + - php: '7.4' + os: macos-latest + yaml: '^5.0' + + runs-on: ${{ matrix.os }} env: YAML: ${{ matrix.yaml }} From a93b90b38c2e619c99f052db42334647f7ffc33c Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Tue, 15 Dec 2020 21:55:09 +0100 Subject: [PATCH 23/39] Update composer.json --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 81b89d0c..c4326258 100644 --- a/composer.json +++ b/composer.json @@ -1,6 +1,6 @@ { "name": "cebe/php-openapi", - "description": "READ OpenAPI yaml files and make the content accessable in PHP objects.", + "description": "Read and write OpenAPI yaml/json files and make the content accessable in PHP objects.", "keywords": ["openapi"], "homepage": "https://github.com/cebe/php-openapi#readme", "type": "library", From 1f7504aeacaa59f4d751afac71cbec433e8c995e Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Tue, 15 Dec 2020 22:02:32 +0100 Subject: [PATCH 24/39] github actions: improve install cache --- .github/workflows/php.yml | 6 ++---- Makefile | 4 ++-- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index f990eaf6..7cf622b1 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -56,13 +56,11 @@ jobs: uses: actions/cache@v2 with: path: ${{ env.COMPOSER_CACHE_DIR }} - key: php${{ matrix.php }}-composer-${{ hashFiles('**/composer.json') }} - restore-keys: | - php${{ matrix.php }}-composer- + key: php${{ matrix.php }}-os${{ matrix.os }}-yaml${{ matrix.yaml }}-composer-${{ hashFiles('**/composer.json') }} - name: Validate composer.json and composer.lock - run: composer validate + run: composer validate --ansi - name: Install dependencies if: steps.composer-cache.outputs.cache-hit != 'true' diff --git a/Makefile b/Makefile index 467f382d..8d420b6f 100644 --- a/Makefile +++ b/Makefile @@ -17,11 +17,11 @@ fix-style: vendor/bin/php-cs-fixer fix src/ --diff install: - composer install --prefer-dist --no-interaction --no-progress --no-suggest + composer install --prefer-dist --no-interaction --no-progress --no-suggest --ansi yarn install test: - php $(PHPARGS) $(XPHPARGS) vendor/bin/phpunit --verbose $(TESTCASE) + php $(PHPARGS) $(XPHPARGS) vendor/bin/phpunit --verbose --color $(TESTCASE) php $(PHPARGS) $(XPHPARGS) bin/php-openapi validate tests/spec/data/recursion.json php $(PHPARGS) $(XPHPARGS) bin/php-openapi validate tests/spec/data/recursion2.yaml From da8d4f5fb99a76ff6a8ae8d019c28222943f4958 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Tue, 15 Dec 2020 22:10:01 +0100 Subject: [PATCH 25/39] github actions --- .github/workflows/php.yml | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index 7cf622b1..2464d810 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -45,7 +45,7 @@ jobs: tools: composer:v2 - name: Determine composer cache directory on Linux - if: matrix.os == 'ubuntu-latest' + if: matrix.os != 'windows-latest' run: echo "COMPOSER_CACHE_DIR=$(composer config cache-dir)" >> $GITHUB_ENV - name: Determine composer cache directory on Windows @@ -62,12 +62,16 @@ jobs: - name: Validate composer.json and composer.lock run: composer validate --ansi - - name: Install dependencies - if: steps.composer-cache.outputs.cache-hit != 'true' + - name: Install dependencies (Linux) + if: matrix.os != 'windows-latest' run: | make install composer require symfony/yaml:"${YAML}" --prefer-dist --no-interaction --ansi + - name: Install dependencies (Windows) + if: matrix.os == 'windows-latest' + run: make install + - name: Validate test data run: make lint From 15936b298ba7ee5d655db94547917c37f333d04c Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Tue, 15 Dec 2020 23:46:56 +0100 Subject: [PATCH 26/39] fix tests, setting explicit versions for symfony that have the least amount of bugs --- .github/workflows/php.yml | 3 ++- composer.json | 2 +- tests/spec/ReferenceTest.php | 8 ++++---- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index 2464d810..f710bb0e 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -15,7 +15,8 @@ jobs: # os: [ubuntu-latest, macos-latest, windows-latest] os: [ubuntu-latest] php: ['7.1', '7.2', '7.3', '7.4', '8.0'] - yaml: ['^5.0', '^4.3', '^3.4'] + # max 4.4.16, see https://github.com/symfony/symfony/issues/39521 + yaml: ['^5.0', '4.4.16', '^3.4'] exclude: # Symfony YAML does not run on PHP 7.1 - php: '7.1' diff --git a/composer.json b/composer.json index c4326258..836b7596 100644 --- a/composer.json +++ b/composer.json @@ -91,7 +91,7 @@ "source": { "url": "https://github.com/Nexmo/api-specification", "type": "git", - "reference": "master" + "reference": "voice-2.0.0" } } } diff --git a/tests/spec/ReferenceTest.php b/tests/spec/ReferenceTest.php index 29928b4c..c7068f20 100644 --- a/tests/spec/ReferenceTest.php +++ b/tests/spec/ReferenceTest.php @@ -433,12 +433,12 @@ public function testTransitiveReferenceOverTwoFiles() /pet: get: responses: - 200: + '200': description: 'return a pet' /cat: get: responses: - 200: + '200': description: 'return a cat' YAML @@ -461,7 +461,7 @@ public function testResolveRelativePathInline() /pet: get: responses: - 200: + '200': description: 'return a pet' components: schemas: @@ -507,7 +507,7 @@ public function testResolveRelativePathAll() /pet: get: responses: - 200: + '200': description: 'return a pet' components: schemas: From fa40bf8a0052d630dc1c65a1cd77c0c664f85bc5 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Tue, 15 Dec 2020 23:59:53 +0100 Subject: [PATCH 27/39] fix code style --- Makefile | 6 +++--- src/ReferenceContext.php | 3 +-- src/spec/Reference.php | 3 +-- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/Makefile b/Makefile index 8d420b6f..72c05d9d 100644 --- a/Makefile +++ b/Makefile @@ -11,10 +11,10 @@ all: check-style: php-cs-fixer.phar PHP_CS_FIXER_IGNORE_ENV=1 ./php-cs-fixer.phar fix src/ --diff --dry-run -fix-style: +fix-style: php-cs-fixer.phar vendor/bin/indent --tabs composer.json vendor/bin/indent --spaces .php_cs.dist - vendor/bin/php-cs-fixer fix src/ --diff + ./php-cs-fixer.phar fix src/ --diff install: composer install --prefer-dist --no-interaction --no-progress --no-suggest --ansi @@ -43,7 +43,7 @@ schemas/openapi-v3.0.yaml: vendor/oai/openapi-specification/schemas/v3.0/schema. cp $< $@ php-cs-fixer.phar: - wget https://github.com/FriendsOfPHP/PHP-CS-Fixer/releases/download/v2.16.7/php-cs-fixer.phar && chmod +x php-cs-fixer.phar + wget -q https://github.com/FriendsOfPHP/PHP-CS-Fixer/releases/download/v2.16.7/php-cs-fixer.phar && chmod +x php-cs-fixer.phar # find spec classes that are not mentioned in tests with @covers yet coverage: .php-openapi-covA .php-openapi-covB diff --git a/src/ReferenceContext.php b/src/ReferenceContext.php index c379e7c1..ac5427d7 100644 --- a/src/ReferenceContext.php +++ b/src/ReferenceContext.php @@ -115,7 +115,7 @@ private function reduceDots($path) { $parts = explode('/', ltrim($path, '/')); $c = count($parts); - for($i = 0; $i < $c; $i++) { + for ($i = 0; $i < $c; $i++) { if ($parts[$i] === '.') { unset($parts[$i]); continue; @@ -265,5 +265,4 @@ public function resolveReferenceData($uri, JsonPointer $pointer, $data, $toType) return $referencedObject; } - } diff --git a/src/spec/Reference.php b/src/spec/Reference.php index 49f735d2..9fff04bd 100644 --- a/src/spec/Reference.php +++ b/src/spec/Reference.php @@ -187,7 +187,6 @@ public function resolve(ReferenceContext $context = null) } try { if ($jsonReference->getDocumentUri() === '') { - if ($context->mode === ReferenceContext::RESOLVE_MODE_INLINE) { return $this; } @@ -298,7 +297,7 @@ private function adjustRelativeReferences($referencedDocument, $basePath, $baseD $baseDocument = $referencedDocument; } - foreach($referencedDocument as $key => $value) { + foreach ($referencedDocument as $key => $value) { if ($key === '$ref' && is_string($value)) { if (isset($value[0]) && $value[0] === '#') { // direcly inline references in the same document, From 201ab9411e828edf0e81ffa5a6fb48935363e11b Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Wed, 16 Dec 2020 00:06:37 +0100 Subject: [PATCH 28/39] playing the symfony/yaml version game... --- .github/workflows/php.yml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index f710bb0e..2a2ff67f 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -16,20 +16,21 @@ jobs: os: [ubuntu-latest] php: ['7.1', '7.2', '7.3', '7.4', '8.0'] # max 4.4.16, see https://github.com/symfony/symfony/issues/39521 - yaml: ['^5.0', '4.4.16', '^3.4'] + # max 5.1.8, see https://github.com/symfony/symfony/issues/39521 + yaml: ['^5.1.8', '4.4.16', '^3.4'] exclude: # Symfony YAML does not run on PHP 7.1 - php: '7.1' yaml: '^5.0' - include: + include: - php: '7.4' os: windows-latest yaml: '^5.0' - php: '7.4' os: macos-latest yaml: '^5.0' - - + + runs-on: ${{ matrix.os }} env: YAML: ${{ matrix.yaml }} From b2b2ee6e1c777b906b3a0ca0c1940c78d77400c0 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Wed, 16 Dec 2020 00:09:46 +0100 Subject: [PATCH 29/39] mac and windows fixes --- .github/workflows/php.yml | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index 2a2ff67f..ded17f89 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -21,14 +21,14 @@ jobs: exclude: # Symfony YAML does not run on PHP 7.1 - php: '7.1' - yaml: '^5.0' + yaml: '^5.1.8' include: - php: '7.4' os: windows-latest - yaml: '^5.0' + yaml: '^5.1.8' - php: '7.4' os: macos-latest - yaml: '^5.0' + yaml: '^5.1.8' runs-on: ${{ matrix.os }} @@ -72,19 +72,25 @@ jobs: - name: Install dependencies (Windows) if: matrix.os == 'windows-latest' - run: make install + run: | + make install + composer require symfony/yaml:^5.1.8 --prefer-dist --no-interaction --ansi - name: Validate test data + if: matrix.os == 'ubuntu-latest' run: make lint - name: PHP Stan analysis + if: matrix.os == 'ubuntu-latest' run: make stan - name: PHPUnit tests run: make test - name: Check code style + if: matrix.os == 'ubuntu-latest' run: make check-style - name: Code coverage + if: matrix.os == 'ubuntu-latest' run: make coverage From 85a812c0bd529b690755cb4ca7c0caad4499d757 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Wed, 16 Dec 2020 00:12:30 +0100 Subject: [PATCH 30/39] fix symfony/yaml version --- .github/workflows/php.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index ded17f89..31d202e6 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -17,18 +17,18 @@ jobs: php: ['7.1', '7.2', '7.3', '7.4', '8.0'] # max 4.4.16, see https://github.com/symfony/symfony/issues/39521 # max 5.1.8, see https://github.com/symfony/symfony/issues/39521 - yaml: ['^5.1.8', '4.4.16', '^3.4'] + yaml: ['5.1.8', '4.4.16', '^3.4'] exclude: # Symfony YAML does not run on PHP 7.1 - php: '7.1' - yaml: '^5.1.8' + yaml: '5.1.8' include: - php: '7.4' os: windows-latest - yaml: '^5.1.8' + yaml: '5.1.8' - php: '7.4' os: macos-latest - yaml: '^5.1.8' + yaml: '5.1.8' runs-on: ${{ matrix.os }} @@ -74,7 +74,7 @@ jobs: if: matrix.os == 'windows-latest' run: | make install - composer require symfony/yaml:^5.1.8 --prefer-dist --no-interaction --ansi + composer require symfony/yaml:5.1.8 --prefer-dist --no-interaction --ansi - name: Validate test data if: matrix.os == 'ubuntu-latest' From dd8770bee7ec64755d888636f41f419a5c0c42ed Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Wed, 16 Dec 2020 00:29:25 +0100 Subject: [PATCH 31/39] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 3e6ec4b5..b21f8340 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ It also provides a CLI tool for validating and converting OpenAPI 3.0.x Descript [![Latest Stable Version](https://poser.pugx.org/cebe/php-openapi/v/stable)](https://packagist.org/packages/cebe/php-openapi) [![Total Downloads](https://poser.pugx.org/cebe/php-openapi/downloads)](https://packagist.org/packages/cebe/php-openapi) -[![Build Status](https://travis-ci.org/cebe/php-openapi.svg?branch=master)](https://travis-ci.org/cebe/php-openapi) +[![Build Status](https://github.com/cebe/php-openapi/workflows/PHP%20Composer/badge.svg)](https://github.com/cebe/php-openapi/actions) [![License](https://poser.pugx.org/cebe/php-openapi/license)](https://packagist.org/packages/cebe/php-openapi) @@ -16,7 +16,7 @@ It also provides a CLI tool for validating and converting OpenAPI 3.0.x Descript ## Requirements -- PHP 7.1 or higher +- PHP 7.1 or higher (works fine with PHP 8) ## Used by From 9f10d4347a48454cfbf6a373d3ef0e172f29dc5f Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Wed, 23 Dec 2020 22:20:24 +0100 Subject: [PATCH 32/39] fix test for PHP 7.1 fixes #91 --- tests/spec/ReferenceTest.php | 39 +++++++++++++++++++++++++----------- 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/tests/spec/ReferenceTest.php b/tests/spec/ReferenceTest.php index c7068f20..ebcc0908 100644 --- a/tests/spec/ReferenceTest.php +++ b/tests/spec/ReferenceTest.php @@ -423,8 +423,7 @@ public function testTransitiveReferenceOverTwoFiles() $yaml = \cebe\openapi\Writer::writeToYaml($openapi); - $this->assertEquals( -<<assertEquals(str_replace("'200':", "200:", $expected), $yaml, $yaml); + } else { + $this->assertEquals($expected, $yaml, $yaml); + } } public function testResolveRelativePathInline() @@ -451,8 +456,7 @@ public function testResolveRelativePathInline() $yaml = \cebe\openapi\Writer::writeToYaml($openapi); - $this->assertEquals( -<<assertEquals(str_replace("'200':", "200:", $expected), $yaml, $yaml); + } else { + $this->assertEquals($expected, $yaml, $yaml); + } } public function testResolveRelativePathAll() @@ -497,8 +507,7 @@ public function testResolveRelativePathAll() $yaml = \cebe\openapi\Writer::writeToYaml($openapi); - $this->assertEquals( -<<assertEquals(str_replace("'200':", "200:", $expected), $yaml, $yaml); + } else { + $this->assertEquals($expected, $yaml, $yaml); + } } } From acb9bdd45d05c8e1a3c3929b1fd784f771592b47 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Wed, 23 Dec 2020 22:33:09 +0100 Subject: [PATCH 33/39] WIP fixing windows build, #90 --- .github/workflows/php.yml | 15 ++++++++++----- Makefile | 4 ++-- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index 31d202e6..5b0197f1 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -46,11 +46,11 @@ jobs: coverage: pcov tools: composer:v2 - - name: Determine composer cache directory on Linux + - name: Determine composer cache directory (Linux/MacOS) if: matrix.os != 'windows-latest' run: echo "COMPOSER_CACHE_DIR=$(composer config cache-dir)" >> $GITHUB_ENV - - name: Determine composer cache directory on Windows + - name: Determine composer cache directory (Windows) if: matrix.os == 'windows-latest' run: echo "COMPOSER_CACHE_DIR=~\AppData\Local\Composer" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append @@ -64,7 +64,7 @@ jobs: - name: Validate composer.json and composer.lock run: composer validate --ansi - - name: Install dependencies (Linux) + - name: Install dependencies (Linux/MacOS) if: matrix.os != 'windows-latest' run: | make install @@ -73,7 +73,7 @@ jobs: - name: Install dependencies (Windows) if: matrix.os == 'windows-latest' run: | - make install + composer install --prefer-dist --no-interaction --no-progress --ansi composer require symfony/yaml:5.1.8 --prefer-dist --no-interaction --ansi - name: Validate test data @@ -84,9 +84,14 @@ jobs: if: matrix.os == 'ubuntu-latest' run: make stan - - name: PHPUnit tests + - name: PHPUnit tests (Linux/MacOS) + if: matrix.os != 'windows-latest' run: make test + - name: PHPUnit tests (Windows) + if: matrix.os == 'windows-latest' + run: vendor/phpunit/phpunit/phpunit --colors=always + - name: Check code style if: matrix.os == 'ubuntu-latest' run: make check-style diff --git a/Makefile b/Makefile index 72c05d9d..627df52f 100644 --- a/Makefile +++ b/Makefile @@ -17,11 +17,11 @@ fix-style: php-cs-fixer.phar ./php-cs-fixer.phar fix src/ --diff install: - composer install --prefer-dist --no-interaction --no-progress --no-suggest --ansi + composer install --prefer-dist --no-interaction --no-progress --ansi yarn install test: - php $(PHPARGS) $(XPHPARGS) vendor/bin/phpunit --verbose --color $(TESTCASE) + php $(PHPARGS) $(XPHPARGS) vendor/bin/phpunit --verbose --colors=always $(TESTCASE) php $(PHPARGS) $(XPHPARGS) bin/php-openapi validate tests/spec/data/recursion.json php $(PHPARGS) $(XPHPARGS) bin/php-openapi validate tests/spec/data/recursion2.yaml From 3da25a3da2d5d74b2e596cbf9a7dde2f972c410a Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Thu, 24 Dec 2020 00:00:40 +0100 Subject: [PATCH 34/39] WIP fixing windows build, #90 --- .github/workflows/php.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index 5b0197f1..de060f21 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -90,7 +90,7 @@ jobs: - name: PHPUnit tests (Windows) if: matrix.os == 'windows-latest' - run: vendor/phpunit/phpunit/phpunit --colors=always + run: vendor/bin/phpunit --colors=always - name: Check code style if: matrix.os == 'ubuntu-latest' From f77a4722a8e39ab9e48e7df63c5646d74d0b0c72 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Thu, 24 Dec 2020 00:17:50 +0100 Subject: [PATCH 35/39] WIP fixing windows build, #90 --- tests/spec/ReferenceTest.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/spec/ReferenceTest.php b/tests/spec/ReferenceTest.php index ebcc0908..bb555b0a 100644 --- a/tests/spec/ReferenceTest.php +++ b/tests/spec/ReferenceTest.php @@ -141,6 +141,7 @@ public function testResolveFile() $file = __DIR__ . '/data/reference/base.yaml'; if (stripos(PHP_OS, 'WIN') === 0) { $yaml = str_replace('##ABSOLUTEPATH##', 'file:///' . strtr(dirname($file), [' ' => '%20', '\\' => '/']), file_get_contents($file)); + throw new \Exception($yaml); } else { $yaml = str_replace('##ABSOLUTEPATH##', 'file://' . dirname($file), file_get_contents($file)); } @@ -441,6 +442,8 @@ public function testTransitiveReferenceOverTwoFiles() description: 'return a cat' YAML; + // remove line endings to make string equal on windows + $expected = preg_replace('~\R~', "\n", $expected); if (PHP_VERSION_ID < 70200) { // PHP <7.2 returns numeric properties in yaml maps as integer, since 7.2 these are string // probably related to https://www.php.net/manual/de/migration72.incompatible.php#migration72.incompatible.object-array-casts @@ -492,6 +495,8 @@ public function testResolveRelativePathInline() description: 'A Cat' YAML; + // remove line endings to make string equal on windows + $expected = preg_replace('~\R~', "\n", $expected); if (PHP_VERSION_ID < 70200) { // PHP <7.2 returns numeric properties in yaml maps as integer, since 7.2 these are string // probably related to https://www.php.net/manual/de/migration72.incompatible.php#migration72.incompatible.object-array-casts @@ -560,6 +565,8 @@ public function testResolveRelativePathAll() description: 'A Cat' YAML; + // remove line endings to make string equal on windows + $expected = preg_replace('~\R~', "\n", $expected); if (PHP_VERSION_ID < 70200) { // PHP <7.2 returns numeric properties in yaml maps as integer, since 7.2 these are string // probably related to https://www.php.net/manual/de/migration72.incompatible.php#migration72.incompatible.object-array-casts From 97f6b7ad99e7ae0dc8ea7251374d632e9c8947c3 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Thu, 24 Dec 2020 00:33:10 +0100 Subject: [PATCH 36/39] WIP fixing windows build, #90 --- tests/spec/ReferenceTest.php | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/tests/spec/ReferenceTest.php b/tests/spec/ReferenceTest.php index bb555b0a..40307fd2 100644 --- a/tests/spec/ReferenceTest.php +++ b/tests/spec/ReferenceTest.php @@ -136,15 +136,20 @@ public function testResolveCyclicReferenceInDocument() $this->assertSame($openapi->components->examples['frog-example'], $refExample); } - public function testResolveFile() + private function createFileUri($file) { - $file = __DIR__ . '/data/reference/base.yaml'; if (stripos(PHP_OS, 'WIN') === 0) { - $yaml = str_replace('##ABSOLUTEPATH##', 'file:///' . strtr(dirname($file), [' ' => '%20', '\\' => '/']), file_get_contents($file)); - throw new \Exception($yaml); + return 'file:///' . strtr($file, [' ' => '%20', '\\' => '/']); } else { - $yaml = str_replace('##ABSOLUTEPATH##', 'file://' . dirname($file), file_get_contents($file)); + return 'file://' . $file; } + } + + public function testResolveFile() + { + $file = __DIR__ . '/data/reference/base.yaml'; + $yaml = str_replace('##ABSOLUTEPATH##', $this->createFileUri(dirname($file)), file_get_contents($file)); + /** @var $openapi OpenApi */ $openapi = Reader::readFromYaml($yaml); @@ -296,7 +301,7 @@ enum: YAML; $openapi = Reader::readFromYaml($schema); - $openapi->resolveReferences(new \cebe\openapi\ReferenceContext($openapi, 'file://' . __DIR__ . '/data/reference/definitions.yaml')); + $openapi->resolveReferences(new \cebe\openapi\ReferenceContext($openapi, $this->createFileUri(__DIR__ . '/data/reference/definitions.yaml'))); $this->assertTrue(isset($openapi->components->schemas['Pet'])); $this->assertEquals(['One', 'Two'], $openapi->components->schemas['Pet']->properties['typeA']->enum); @@ -377,7 +382,7 @@ public function testTransitiveReferenceToFile() YAML; $openapi = Reader::readFromYaml($schema); - $openapi->resolveReferences(new \cebe\openapi\ReferenceContext($openapi, 'file://' . __DIR__ . '/data/reference/definitions.yaml')); + $openapi->resolveReferences(new \cebe\openapi\ReferenceContext($openapi, $this->createFileUri(__DIR__ . '/data/reference/definitions.yaml'))); $this->assertTrue(isset($openapi->components->schemas['Dog'])); $this->assertEquals('object', $openapi->components->schemas['Dog']->type); From 2f71121ba0e40d0cf102c0d9af55dac625bafe26 Mon Sep 17 00:00:00 2001 From: insolita Date: Sun, 27 Dec 2020 20:58:54 +0800 Subject: [PATCH 37/39] fix #92 --- src/ReferenceContext.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ReferenceContext.php b/src/ReferenceContext.php index ac5427d7..3d824a63 100644 --- a/src/ReferenceContext.php +++ b/src/ReferenceContext.php @@ -92,7 +92,7 @@ private function normalizeUri($uri) } if (stripos(PHP_OS, 'WIN') === 0 && strncmp(substr($uri, 1), ':\\', 2) === 0) { $uri = $this->reduceDots($uri); - return "file:///" . strtr($uri, [' ' => '%20', '\\' => '/']); + return "file://" . strtr($uri, [' ' => '%20', '\\' => '/']); } throw new UnresolvableReferenceException('Can not resolve references for a specification given as a relative path.'); } From 38d985aab431604a1b7fae6356614c1301e9f0e0 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Thu, 31 Dec 2020 00:57:14 +0100 Subject: [PATCH 38/39] drop travis-CI --- .gitignore | 2 ++ .travis.yml | 62 ----------------------------------------------------- 2 files changed, 2 insertions(+), 62 deletions(-) delete mode 100644 .travis.yml diff --git a/.gitignore b/.gitignore index fe462e09..c86af6fe 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,10 @@ /vendor /composer.lock +/composer.phar /node_modules /.php_cs.cache +/.phpunit.result.cache php-cs-fixer.phar diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index ee9e4f98..00000000 --- a/.travis.yml +++ /dev/null @@ -1,62 +0,0 @@ -language: php - -matrix: - include: - # linux tests - - php: '7.1' - env: YAML=^3.4 - - php: '7.1' - env: YAML=~4.3.0 - - php: '7.2' - env: YAML=^3.4 - - php: '7.2' - env: YAML=~4.3.0 - - php: '7.3' - env: YAML=^3.4 - - php: '7.3' - env: YAML=~4.3.0 - - php: '7.3' - env: YAML=^5.0 - - php: '7.4' - env: YAML=^3.4 - - php: '7.4' - env: YAML=~4.3.0 - - php: '7.4' - env: YAML=^5.0 - - php: nightly - env: YAML=~4.4.0 - # windows tests - # https://travis-ci.community/t/where-to-contribute-php-support-for-windows/304 - - os: windows - language: sh - before_install: - - export PATH="/c/tools/composer:/c/tools/php73:$PATH" - - echo "$PATH" - - choco install php --version 7.3.5 - - php -i - - cat /c/tools/php73/php.ini - #- choco install make - - ls /c/tools/php73 - - ls /c/tools/php73/ext - #- ls /c/tools/composer - - wget https://raw.githubusercontent.com/composer/getcomposer.org/76a7060ccb93902cd7576b67264ad91c8a2700e2/web/installer -O - -q | php -dextension=/c/tools/php73/ext/php_openssl.dll -- - - ls - install: - - php -dextension=/c/tools/php73/ext/php_openssl.dll -dextension=/c/tools/php73/ext/php_mbstring.dll composer.phar install --prefer-dist --no-interaction - - php -dextension=/c/tools/php73/ext/php_openssl.dll -dextension=/c/tools/php73/ext/php_mbstring.dll composer.phar require symfony/yaml:"~4.3.0" --prefer-dist --no-interaction - script: php -dextension=/c/tools/php73/ext/php_openssl.dll -dextension=/c/tools/php73/ext/php_mbstring.dll vendor/phpunit/phpunit/phpunit - - # allow php nightly to fail until there is a phpunit version that supports PHP 8 - allow_failures: - - php: nightly - -install: - - make install - - composer require symfony/yaml:"${YAML}" --prefer-dist --no-interaction -script: - - make lint - - make stan - - make test - - make check-style - - make coverage - From 310de0290cfa9597f8bd3444cb7f8e1682bbbeeb Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Thu, 31 Dec 2020 01:41:54 +0100 Subject: [PATCH 39/39] added cli option to inline references fix #63 --- README.md | 15 ++++++++++++++- bin/php-openapi | 42 ++++++++++++++++++++++++++++++++++-------- composer.json | 6 +++--- 3 files changed, 51 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index b21f8340..0bdb7981 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,6 @@ do awesome work: Exits with code 2 on validation errors, 1 on other errors and 0 on success. convert Convert a JSON or YAML input file to JSON or YAML output file. - References are being resolved so the output will be a single API Description file. If no input file is specified input will be read from STDIN. If no output file is specified output will be written to STDOUT. @@ -63,6 +62,19 @@ do awesome work: to do so, you may specify --read-yaml or --read-json to force the input file type. and --write-yaml or --write-json to force the output file type. + By default all references are resolved (replaced with the object refered to). You can control + handling of references with the following arguments: + + --resolve-none Do not resolve references. + --resolve-external Only resolve references that point to external files. + This process is often referred to as "inlining". + --resolve-all Resolve all references (default). + Recursive pointers will stay references. + + inline Convert a JSON or YAML input file to JSON or YAML output file and + resolve all external references. The output will be a single API Description file. + This is a shortcut for calling convert --resolve-external. + help Shows this usage information. Options: @@ -71,6 +83,7 @@ do awesome work: --read-yaml force reading input as YAML. Auto-detect if not specified. --write-json force writing output as JSON. Auto-detect if not specified. --write-yaml force writing output as YAML. Auto-detect if not specified. + -s, --silent silent mode. Will hide all success/information messages and only print errors. ### Reading API Description Files diff --git a/bin/php-openapi b/bin/php-openapi index 00730ef6..95052987 100755 --- a/bin/php-openapi +++ b/bin/php-openapi @@ -33,6 +33,7 @@ $inputFormat = null; $outputFile = null; $outputFormat = null; $silentMode = false; +$referenceMode = ReferenceContext::RESOLVE_MODE_ALL; foreach($argv as $k => $arg) { if ($k == 0) { continue; @@ -54,6 +55,15 @@ foreach($argv as $k => $arg) { error("Conflicting arguments: only one of --read-json or --read-yaml is allowed!", "usage"); } break; + case '--resolve-none': + $referenceMode = false; + break; + case '--resolve-external': + $referenceMode = ReferenceContext::RESOLVE_MODE_INLINE; + break; + case '--resolve-all': + $referenceMode = ReferenceContext::RESOLVE_MODE_ALL; + break; case '--write-yaml': if ($outputFormat === null) { $outputFormat = 'yaml'; @@ -89,6 +99,11 @@ foreach($argv as $k => $arg) { } else { if ($command === null) { $command = $arg; + // inline is an alias for "convert --resolve-external" + if ($command === 'inline') { + $command = 'convert'; + $referenceMode = ReferenceContext::RESOLVE_MODE_INLINE; + } } elseif ($inputFile === null) { $inputFile = $arg; } elseif ($outputFile === null) { @@ -109,9 +124,7 @@ switch ($command) { $openApi = read_input($inputFile, $inputFormat); $referenceContext = new ReferenceContext($openApi, $inputFile ? realpath($inputFile) : ''); $referenceContext->throwException = false; - // TODO apply reference context mode -// $referenceContext->mode = ReferenceContext::RESOLVE_MODE_ALL; -// $referenceContext->mode = ReferenceContext::RESOLVE_MODE_INLINE; + $referenceContext->mode = ReferenceContext::RESOLVE_MODE_INLINE; $openApi->resolveReferences($referenceContext); $openApi->setDocumentContext($openApi, new \cebe\openapi\json\JsonPointer('')); @@ -189,12 +202,13 @@ switch ($command) { $openApi = read_input($inputFile, $inputFormat); try { - // TODO apply reference context mode -// $referenceContext->mode = ReferenceContext::RESOLVE_MODE_ALL; -// $referenceContext->mode = ReferenceContext::RESOLVE_MODE_INLINE; // set document context for correctly converting recursive references $openApi->setDocumentContext($openApi, new \cebe\openapi\json\JsonPointer('')); - $openApi->resolveReferences(); + if ($referenceMode) { + $referenceContext = new ReferenceContext($openApi, $inputFile ? realpath($inputFile) : ''); + $referenceContext->mode = $referenceMode; + $openApi->resolveReferences($referenceContext); + } } catch (\cebe\openapi\exceptions\UnresolvableReferenceException $e) { error("[\e[33m{$e->context}\e[0m] " . $e->getMessage()); } @@ -310,7 +324,6 @@ Usage: Exits with code 2 on validation errors, 1 on other errors and 0 on success. \Bconvert\C Convert a JSON or YAML input file to JSON or YAML output file. - References are being resolved so the output will be a single API Description file. If no input file is specified input will be read from STDIN. If no output file is specified output will be written to STDOUT. @@ -318,6 +331,19 @@ Usage: to do so, you may specify \Y--read-yaml\C or \Y--read-json\C to force the input file type. and \Y--write-yaml\C or \Y--write-json\C to force the output file type. + By default all references are resolved (replaced with the object refered to). You can control + handling of references with the following arguments: + + \Y--resolve-none\C Do not resolve references. + \Y--resolve-external\C Only resolve references that point to external files. + This process is often referred to as "inlining". + \Y--resolve-all\C Resolve all references (default). + Recursive pointers will stay references. + + \Binline\C Convert a JSON or YAML input file to JSON or YAML output file and + resolve all external references. The output will be a single API Description file. + This is a shortcut for calling \Bconvert\C \Y--resolve-external\C. + \Bhelp\C Shows this usage information. Options: diff --git a/composer.json b/composer.json index 836b7596..eef6513b 100644 --- a/composer.json +++ b/composer.json @@ -27,7 +27,7 @@ "cebe/indent": "*", "phpunit/phpunit": "^6.5 || ^7.5 || ^8.5 || ^9.4", - "oai/openapi-specification": "3.0.2", + "oai/openapi-specification": "3.0.3", "mermade/openapi3-examples": "1.0.0", "apis-guru/openapi-directory": "1.0.0", "nexmo/api-specification": "1.0.0", @@ -51,11 +51,11 @@ "type": "package", "package": { "name": "oai/openapi-specification", - "version": "3.0.2", + "version": "3.0.3", "source": { "url": "https://github.com/OAI/OpenAPI-Specification", "type": "git", - "reference": "3.0.2" + "reference": "3.0.3" } } },