From 3c1871ce1f03b0ecd6273cbf7f0d3e5518d4b5cc Mon Sep 17 00:00:00 2001 From: Anthony Axenov Date: Sun, 17 Sep 2023 21:42:06 +0800 Subject: [PATCH] Conversion to 2.0 (--v2.0), improvements and fixes (#10) First of all, now you can convert a collection manually exported from Postman UI. Until this commit, any collection json had to be inside of root 'collection' object. Postman API returns collections in a such way and that was my case. So any collection exported using UI was mistakenly not detected as correct one. The second thing that it is now possible to convert collections from v2.1 to v2.0 using --v2.0 flag. Even if they are exported via Postman API, of course. Also some important refactorings are here. --- src/Collection.php | 98 +++++++++++++ src/CollectionVersion.php | 12 ++ src/Converters/Abstract/AbstractConverter.php | 38 ++++- src/Converters/Abstract/AbstractRequest.php | 6 +- src/Converters/ConvertFormat.php | 2 + src/Converters/ConverterContract.php | 4 +- .../Postman20/Postman20Converter.php | 133 ++++++++++++++++++ src/FileSystem.php | 9 +- src/{HttpVersions.php => HttpVersion.php} | 2 +- src/Processor.php | 14 +- tests/AbstractRequestTest.php | 4 +- 11 files changed, 296 insertions(+), 26 deletions(-) create mode 100644 src/Collection.php create mode 100644 src/CollectionVersion.php create mode 100644 src/Converters/Postman20/Postman20Converter.php rename src/{HttpVersions.php => HttpVersion.php} (93%) diff --git a/src/Collection.php b/src/Collection.php new file mode 100644 index 0000000..4a032aa --- /dev/null +++ b/src/Collection.php @@ -0,0 +1,98 @@ +collection)) { + $json = $json->collection; + } + $this->json = $json; + } + + /** + * Factory that creates new Collection from content read from file path + * + * @param string $path + * @return static + * @throws JsonException + */ + public static function fromFile(string $path): static + { + $content = file_get_contents(FileSystem::normalizePath($path)); + $json = json_decode($content, flags: JSON_THROW_ON_ERROR); + return new static($json); + } + + /** + * @inheritDoc + */ + public function __toString(): string + { + return json_encode($this->json, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); + } + + /** + * Returns reference to the parsed json structure + * + * @return object + */ + public function &raw(): object + { + return $this->json; + } + + /** + * Returns reference to any part of the parsed json structure + * + * @param string $name + * @return mixed + */ + public function &__get(string $name): mixed + { + return $this->json->$name; + } + + /** + * Returns collection name + * + * @return string + */ + public function name(): string + { + return $this->json->info->name; + } + + /** + * Returns the collection version + * + * @return CollectionVersion + */ + public function version(): CollectionVersion + { + return match (true) { + str_contains($this->json->info->schema, '/v2.0.') => CollectionVersion::Version20, + str_contains($this->json->info->schema, '/v2.1.') => CollectionVersion::Version21, + default => CollectionVersion::Unknown + }; + } +} diff --git a/src/CollectionVersion.php b/src/CollectionVersion.php new file mode 100644 index 0000000..b171c02 --- /dev/null +++ b/src/CollectionVersion.php @@ -0,0 +1,12 @@ +outputPath = FileSystem::makeDir($outputPath); + } + + /** + * Converts collection requests + * + * @param Collection $collection + * @param string $outputPath + * @return void + * @throws CannotCreateDirectoryException + * @throws DirectoryIsNotWriteableException + * @throws Exception + */ + public function convert(Collection $collection, string $outputPath): void + { + $this->prepareOutputDir($outputPath); $this->collection = $collection; $this->setVariables(); foreach ($collection->item as $item) { @@ -94,7 +116,9 @@ abstract class AbstractConverter implements ConverterContract */ protected function isItemFolder(object $item): bool { - return !empty($item->item) && empty($item->request); + return !empty($item->item) + && is_array($item->item) + && empty($item->request); } /** diff --git a/src/Converters/Abstract/AbstractRequest.php b/src/Converters/Abstract/AbstractRequest.php index 858123e..ace779b 100644 --- a/src/Converters/Abstract/AbstractRequest.php +++ b/src/Converters/Abstract/AbstractRequest.php @@ -8,7 +8,7 @@ use PmConverter\Converters\RequestContract; use PmConverter\Exceptions\{ EmptyHttpVerbException, InvalidHttpVersionException}; -use PmConverter\HttpVersions; +use PmConverter\HttpVersion; use Stringable; /** @@ -61,9 +61,9 @@ abstract class AbstractRequest implements Stringable, RequestContract */ public function setHttpVersion(float $version): static { - if (!in_array($version, HttpVersions::values())) { + if (!in_array($version, HttpVersion::values())) { throw new InvalidHttpVersionException( - 'Only these HTTP versions are supported: ' . implode(', ', HttpVersions::values()) + 'Only these HTTP versions are supported: ' . implode(', ', HttpVersion::values()) ); } $this->httpVersion = $version; diff --git a/src/Converters/ConvertFormat.php b/src/Converters/ConvertFormat.php index 73800b6..9aa6c0a 100644 --- a/src/Converters/ConvertFormat.php +++ b/src/Converters/ConvertFormat.php @@ -8,6 +8,7 @@ namespace PmConverter\Converters; use PmConverter\Converters\{ Curl\CurlConverter, Http\HttpConverter, + Postman20\Postman20Converter, Wget\WgetConverter}; enum ConvertFormat: string @@ -15,4 +16,5 @@ enum ConvertFormat: string case Http = HttpConverter::class; case Curl = CurlConverter::class; case Wget = WgetConverter::class; + case Postman20 = Postman20Converter::class; } diff --git a/src/Converters/ConverterContract.php b/src/Converters/ConverterContract.php index 1f280de..d8116dc 100644 --- a/src/Converters/ConverterContract.php +++ b/src/Converters/ConverterContract.php @@ -4,8 +4,10 @@ declare(strict_types=1); namespace PmConverter\Converters; +use PmConverter\Collection; + interface ConverterContract { - public function convert(object $collection, string $outputPath): void; + public function convert(Collection $collection, string $outputPath): void; public function getOutputPath(): string; } diff --git a/src/Converters/Postman20/Postman20Converter.php b/src/Converters/Postman20/Postman20Converter.php new file mode 100644 index 0000000..370044f --- /dev/null +++ b/src/Converters/Postman20/Postman20Converter.php @@ -0,0 +1,133 @@ +collection = $collection; + $this->collection->info->schema = str_replace('/v2.1.', '/v2.0.', $this->collection->info->schema); + $this->prepareOutputDir($outputPath); + $this->convertAuth($this->collection->raw()); + foreach ($this->collection->item as $item) { + $this->convertItem($item); + } + $this->writeCollection(); + } + + /** + * Writes converted collection into file + * + * @return bool + * @throws CannotCreateDirectoryException + * @throws DirectoryIsNotWriteableException + */ + protected function writeCollection(): bool + { + $filedir = FileSystem::makeDir($this->outputPath); + $filepath = sprintf('%s%s%s.%s', $filedir, DIRECTORY_SEPARATOR, $this->collection->name(), static::FILE_EXT); + return file_put_contents($filepath, $this->collection) > 0; + } + + /** + * Changes some requests fields in place + * + * @param mixed $item + * @return void + */ + protected function convertItem(mixed $item): void + { + if ($this->isItemFolder($item)) { + foreach ($item->item as $subitem) { + if ($this->isItemFolder($subitem)) { + $this->convertItem($subitem); + } else { + $this->convertAuth($subitem->request); + $this->convertRequestUrl($subitem->request); + $this->convertResponseUrls($subitem->response); + } + } + } else { + $this->convertAuth($item->request); + $this->convertRequestUrl($item->request); + $this->convertResponseUrls($item->response); + } + } + + /** + * Converts auth object from v2.1 to v2.0 + * + * @param object $request + * @return void + */ + protected function convertAuth(object $request): void + { + if (empty($request->auth)) { + return; + } + $type = $request->auth->type; + if ($type !== 'noauth' && is_array($request->auth->$type)) { // bearer + $auth = []; + foreach ($request->auth->$type as $param) { + $auth[$param->key] = $param->value; + } + $request->auth->$type = (object)$auth; + } + } + + /** + * Converts requests URLs from object v2.1 to string v2.0 + * + * @param object $request + * @return void + */ + protected function convertRequestUrl(object $request): void + { + if (is_object($request->url)) { + $request->url = $request->url->raw; + } + } + + /** + * Converts URLs response examples from object v2.1 to string v2.0 + * + * @param array $responses + * @return void + */ + protected function convertResponseUrls(array $responses): void + { + foreach ($responses as $response) { + if (is_object($response->originalRequest->url)) { + $raw = $response->originalRequest->url->raw; + $response->originalRequest->url = $raw; + } + } + } +} diff --git a/src/FileSystem.php b/src/FileSystem.php index 1a63685..7cd4219 100644 --- a/src/FileSystem.php +++ b/src/FileSystem.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace PmConverter; +use JsonException; use PmConverter\Exceptions\{ CannotCreateDirectoryException, DirectoryIsNotReadableException, @@ -110,16 +111,14 @@ class FileSystem * * @param string $path * @return bool + * @throws JsonException */ public static function isCollectionFile(string $path): bool { - $path = static::normalizePath($path); - return !empty($path = trim($path)) + return (!empty($path = trim(static::normalizePath($path)))) && str_ends_with($path, '.postman_collection.json') && file_exists($path) && is_readable($path) - && ($json = json_decode(file_get_contents($path), true)) - && json_last_error() === JSON_ERROR_NONE - && isset($json['collection']['info']['name']); + && Collection::fromFile($path)->version() !== CollectionVersion::Unknown; } } diff --git a/src/HttpVersions.php b/src/HttpVersion.php similarity index 93% rename from src/HttpVersions.php rename to src/HttpVersion.php index 461ad42..0c29c9b 100644 --- a/src/HttpVersions.php +++ b/src/HttpVersion.php @@ -4,7 +4,7 @@ declare(strict_types=1); namespace PmConverter; -enum HttpVersions: string +enum HttpVersion: string { case Version10 = '1.0'; case Version11 = '1.1'; diff --git a/src/Processor.php b/src/Processor.php index c74f2e5..05e3137 100644 --- a/src/Processor.php +++ b/src/Processor.php @@ -107,7 +107,7 @@ class Processor $normpath = FileSystem::normalizePath($rawpath); if (!FileSystem::isCollectionFile($normpath)) { throw new InvalidArgumentException( - sprintf("this is not a valid collection file:%s\t%s %s", PHP_EOL, $arg, $rawpath) + sprintf("not a valid collection:%s\t%s %s", PHP_EOL, $arg, $rawpath) ); } $this->collectionPaths[] = $this->argv[$idx + 1]; @@ -156,6 +156,10 @@ class Processor $this->formats[ConvertFormat::Wget->name] = ConvertFormat::Wget; break; + case '--v2.0': + $this->formats[ConvertFormat::Postman20->name] = ConvertFormat::Postman20; + break; + case '--var': [$var, $value] = explode('=', trim($this->argv[$idx + 1])); $this->vars[$var] = $value; @@ -219,12 +223,8 @@ class Processor protected function initCollections(): void { foreach ($this->collectionPaths as $collectionPath) { - $content = file_get_contents(FileSystem::normalizePath($collectionPath)); - $content = json_decode($content, flags: JSON_THROW_ON_ERROR); - if (!property_exists($content, 'collection') || empty($content?->collection)) { - throw new JsonException("not a valid collection: $collectionPath"); - } - $this->collections[$content->collection->info->name] = $content->collection; + $collection = Collection::fromFile($collectionPath); + $this->collections[$collection->name()] = $collection; } unset($this->collectionPaths, $content); } diff --git a/tests/AbstractRequestTest.php b/tests/AbstractRequestTest.php index 89491fe..54c9c2d 100644 --- a/tests/AbstractRequestTest.php +++ b/tests/AbstractRequestTest.php @@ -11,7 +11,7 @@ class AbstractRequestTest extends TestCase { /** * @covers PmConverter\Converters\Abstract\AbstractRequest - * @covers PmConverter\HttpVersions + * @covers PmConverter\HttpVersion * @return void * @throws InvalidHttpVersionException */ @@ -26,7 +26,7 @@ class AbstractRequestTest extends TestCase /** * @covers PmConverter\Converters\Abstract\AbstractRequest * @covers PmConverter\Converters\Abstract\AbstractRequest::getVerb() - * @covers PmConverter\HttpVersions + * @covers PmConverter\HttpVersion * @return void * @throws InvalidHttpVersionException */