From 3c1871ce1f03b0ecd6273cbf7f0d3e5518d4b5cc Mon Sep 17 00:00:00 2001 From: Anthony Axenov Date: Sun, 17 Sep 2023 21:42:06 +0800 Subject: [PATCH 1/5] 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 */ From 01d29ee023b12a35ae718385e89af97c4853618b Mon Sep 17 00:00:00 2001 From: Anthony Axenov Date: Sun, 17 Sep 2023 23:59:37 +0800 Subject: [PATCH 2/5] Initial and very naive conversion v2.0 => v2.1 (#10) --- composer.json | 3 +- src/Collection.php | 1 + src/Converters/ConvertFormat.php | 2 + .../Postman20/Postman20Converter.php | 7 +- .../Postman21/Postman21Converter.php | 164 ++++++++++++++++++ src/Environment.php | 10 +- src/Processor.php | 14 +- 7 files changed, 190 insertions(+), 11 deletions(-) create mode 100644 src/Converters/Postman21/Postman21Converter.php diff --git a/composer.json b/composer.json index 8c5cf7c..fe966c0 100644 --- a/composer.json +++ b/composer.json @@ -13,7 +13,8 @@ "keywords": ["postman", "collection", "converter", "http", "wget", "curl", "api", "convert"], "require": { "php": "^8.1", - "ext-json": "*" + "ext-json": "*", + "ext-mbstring": "*" }, "bin": ["pm-convert"], "autoload": { diff --git a/src/Collection.php b/src/Collection.php index 4a032aa..891419b 100644 --- a/src/Collection.php +++ b/src/Collection.php @@ -12,6 +12,7 @@ use Stringable; * * @property array|object $item * @property object $info + * @property object|null $variable */ class Collection implements Stringable { diff --git a/src/Converters/ConvertFormat.php b/src/Converters/ConvertFormat.php index 9aa6c0a..3bebb54 100644 --- a/src/Converters/ConvertFormat.php +++ b/src/Converters/ConvertFormat.php @@ -9,6 +9,7 @@ use PmConverter\Converters\{ Curl\CurlConverter, Http\HttpConverter, Postman20\Postman20Converter, + Postman21\Postman21Converter, Wget\WgetConverter}; enum ConvertFormat: string @@ -17,4 +18,5 @@ enum ConvertFormat: string case Curl = CurlConverter::class; case Wget = WgetConverter::class; case Postman20 = Postman20Converter::class; + case Postman21 = Postman21Converter::class; } diff --git a/src/Converters/Postman20/Postman20Converter.php b/src/Converters/Postman20/Postman20Converter.php index 370044f..2730d7f 100644 --- a/src/Converters/Postman20/Postman20Converter.php +++ b/src/Converters/Postman20/Postman20Converter.php @@ -19,7 +19,7 @@ class Postman20Converter extends AbstractConverter implements ConverterContract { protected const FILE_EXT = 'v20.postman_collection.json'; - protected const OUTPUT_DIR = 'v2.0'; + protected const OUTPUT_DIR = 'pm-v2.0'; /** * Converts collection requests @@ -93,7 +93,7 @@ class Postman20Converter extends AbstractConverter implements ConverterContract return; } $type = $request->auth->type; - if ($type !== 'noauth' && is_array($request->auth->$type)) { // bearer + if ($type !== 'noauth' && is_array($request->auth->$type)) { $auth = []; foreach ($request->auth->$type as $param) { $auth[$param->key] = $param->value; @@ -125,8 +125,7 @@ class Postman20Converter extends AbstractConverter implements ConverterContract { foreach ($responses as $response) { if (is_object($response->originalRequest->url)) { - $raw = $response->originalRequest->url->raw; - $response->originalRequest->url = $raw; + $response->originalRequest->url = $response->originalRequest->url->raw; } } } diff --git a/src/Converters/Postman21/Postman21Converter.php b/src/Converters/Postman21/Postman21Converter.php new file mode 100644 index 0000000..317812a --- /dev/null +++ b/src/Converters/Postman21/Postman21Converter.php @@ -0,0 +1,164 @@ +collection = $collection; + $this->collection->info->schema = str_replace('/v2.0.', '/v2.1.', $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.0 to v2.1 + * + * @param object $request + * @return void + */ + protected function convertAuth(object $request): void + { + if (empty($request->auth)) { + return; + } + $type = $request->auth->type; + if ($type !== 'noauth' && isset($request->auth->$type)) { + $auth = []; + foreach ($request->auth->$type as $key => $value) { + $auth[] = (object)[ + 'key' => $key, + 'value' => $value, + 'type' => 'string', + ]; + } + $request->auth->$type = $auth; + } + } + + /** + * Converts requests URLs from string v2.0 to object v2.1 + * + * @param object $request + * @return void + */ + protected function convertRequestUrl(object $request): void + { + if (is_string($request->url) && mb_strlen($request->url) > 0) { + $data = array_values(array_filter(explode('/', $request->url))); //TODO URL parsing + if (count($data) === 1) { + $url = [ + 'raw' => $request->url, + 'host' => [$data[0] ?? $request->url], + ]; + } else { + $url = [ + 'raw' => $request->url, + 'protocol' => str_replace(':', '', $data[0]), + 'host' => [$data[1] ?? $request->url], + 'path' => array_slice($data, 2), + ]; + } + $request->url = (object)$url; + } + } + + /** + * Converts URLs response examples from string v2.0 to object v2.1 + * + * @param array $responses + * @return void + */ + protected function convertResponseUrls(array $responses): void + { + foreach ($responses as $response) { + if (is_string($response->originalRequest->url)) { + $data = array_values(array_filter(explode('/', $response->originalRequest->url))); //TODO URL parsing + if (count($data) === 1) { + $url = [ + 'raw' => $response->originalRequest->url, + 'host' => [$data[0] ?? $response->originalRequest->url], + ]; + } else { + $url = [ + 'raw' => $response->originalRequest->url, + 'protocol' => str_replace(':', '', $data[0]), + 'host' => [$data[1] ?? $response->originalRequest->url], + 'path' => array_slice($data, 2), + ]; + } + $response->originalRequest->url = (object)$url; + } + } + } +} diff --git a/src/Environment.php b/src/Environment.php index b0d1164..ab3bcaf 100644 --- a/src/Environment.php +++ b/src/Environment.php @@ -12,12 +12,14 @@ class Environment implements \ArrayAccess protected array $vars = []; /** - * @param object $env + * @param object|null $env */ - public function __construct(protected object $env) + public function __construct(protected ?object $env) { - foreach ($env->values as $var) { - $this->vars[static::formatKey($var->key)] = $var->value; + if (!empty($env->values)) { + foreach ($env->values as $var) { + $this->vars[static::formatKey($var->key)] = $var->value; + } } } diff --git a/src/Processor.php b/src/Processor.php index 05e3137..4ddaa2f 100644 --- a/src/Processor.php +++ b/src/Processor.php @@ -54,7 +54,7 @@ class Processor protected array $converters = []; /** - * @var object[] Collections that will be converted into choosen formats + * @var Collection[] Collections that will be converted into choosen formats */ protected array $collections = []; @@ -93,6 +93,7 @@ class Processor * Parses an array of arguments came from cli * * @return void + * @throws JsonException */ protected function parseArgs(): void { @@ -160,6 +161,10 @@ class Processor $this->formats[ConvertFormat::Postman20->name] = ConvertFormat::Postman20; break; + case '--v2.1': + $this->formats[ConvertFormat::Postman21->name] = ConvertFormat::Postman21; + break; + case '--var': [$var, $value] = explode('=', trim($this->argv[$idx + 1])); $this->vars[$var] = $value; @@ -297,8 +302,13 @@ class Processor protected function printStats(int $success, int $count): void { $time = (hrtime(true) - $this->initTime) / 1_000_000; + $timeFmt = 'ms'; + if ($time > 1000) { + $time /= 1000; + $timeFmt = 'sec'; + } $ram = (memory_get_peak_usage(true) - $this->initRam) / 1024 / 1024; - printf('Converted %d of %d in %.3f ms using up to %.3f MiB RAM%s', $success, $count, $time, $ram, PHP_EOL); + printf("Converted %d/%d in %.2f $timeFmt using up to %.2f MiB RAM%s", $success, $count, $time, $ram, PHP_EOL); } /** From 60cad4b501d1be3f17ed8931a06bbc85fb3dbfbd Mon Sep 17 00:00:00 2001 From: Anthony Axenov Date: Mon, 18 Sep 2023 00:33:37 +0800 Subject: [PATCH 3/5] Skip postman schema conversion if it is already same as specified (#10) --- src/Converters/Postman20/Postman20Converter.php | 15 ++++++++++----- src/Converters/Postman21/Postman21Converter.php | 15 ++++++++++----- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/src/Converters/Postman20/Postman20Converter.php b/src/Converters/Postman20/Postman20Converter.php index 2730d7f..76adfc4 100644 --- a/src/Converters/Postman20/Postman20Converter.php +++ b/src/Converters/Postman20/Postman20Converter.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace PmConverter\Converters\Postman20; use PmConverter\Collection; +use PmConverter\CollectionVersion; use PmConverter\Converters\{ Abstract\AbstractConverter, ConverterContract}; @@ -33,12 +34,16 @@ class Postman20Converter extends AbstractConverter implements ConverterContract public function convert(Collection $collection, string $outputPath): void { $this->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); + // if data was exported from API, here is already valid json to + // just flush it in file, otherwise we need to convert it deeper + if ($this->collection->version() === CollectionVersion::Version21) { + $this->collection->info->schema = str_replace('/v2.1.', '/v2.0.', $this->collection->info->schema); + $this->convertAuth($this->collection->raw()); + foreach ($this->collection->item as $item) { + $this->convertItem($item); + } } + $this->prepareOutputDir($outputPath); $this->writeCollection(); } diff --git a/src/Converters/Postman21/Postman21Converter.php b/src/Converters/Postman21/Postman21Converter.php index 317812a..948de30 100644 --- a/src/Converters/Postman21/Postman21Converter.php +++ b/src/Converters/Postman21/Postman21Converter.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace PmConverter\Converters\Postman21; use PmConverter\Collection; +use PmConverter\CollectionVersion; use PmConverter\Converters\{ Abstract\AbstractConverter, ConverterContract}; @@ -33,12 +34,16 @@ class Postman21Converter extends AbstractConverter implements ConverterContract public function convert(Collection $collection, string $outputPath): void { $this->collection = $collection; - $this->collection->info->schema = str_replace('/v2.0.', '/v2.1.', $this->collection->info->schema); - $this->prepareOutputDir($outputPath); - $this->convertAuth($this->collection->raw()); - foreach ($this->collection->item as $item) { - $this->convertItem($item); + // if data was exported from API, here is already valid json to + // just flush it in file, otherwise we need to convert it deeper + if ($this->collection->version() === CollectionVersion::Version20) { + $this->collection->info->schema = str_replace('/v2.0.', '/v2.1.', $this->collection->info->schema); + $this->convertAuth($this->collection->raw()); + foreach ($this->collection->item as $item) { + $this->convertItem($item); + } } + $this->prepareOutputDir($outputPath); $this->writeCollection(); } From c6bdcfe7ccbc04fea9c06d60da035e545d4bf4d7 Mon Sep 17 00:00:00 2001 From: Anthony Axenov Date: Mon, 18 Sep 2023 00:38:40 +0800 Subject: [PATCH 4/5] New flag --all, corrections in --help and README --- README.md | 77 +++++++++++++++++++++++++++++------------------ src/Processor.php | 31 ++++++++++--------- 2 files changed, 64 insertions(+), 44 deletions(-) diff --git a/README.md b/README.md index bad62de..638e5aa 100644 --- a/README.md +++ b/README.md @@ -24,10 +24,9 @@ These formats are supported for now: `http`, `curl`, `wget`. ## Planned features -- conversion between postman schema v2.1 <-> v2.0 (#11); - support as many as possible/necessary of authentication kinds (_currently only `Bearer` supported_); - support as many as possible/necessary of body formats (_currently only `json` and `formdata`_); -- documentation generation support (markdown) with responce examples (if present) (#6); +- documentation generation support (markdown) with response examples (if present) (#6); - maybe some another convert formats (like httpie or something...); - better logging; - 90%+ test coverage, phpcs, psalm, etc.; @@ -65,7 +64,6 @@ Possible ARGUMENTS: -o, --output - a directory OUTPUT_PATH to put results in -e, --env - use environment file with variables to replace in requests --var "NAME=VALUE" - force replace specified env variable called NAME with custom VALUE - (see interpolation notes below) -p, --preserve - do not delete OUTPUT_PATH (if exists) -h, --help - show this help message and exit -v, --version - show version info and exit @@ -78,32 +76,26 @@ OUTPUT_PATH must be a valid path to writeable directory. If -o or -e was specified several times then only last one will be used. Possible FORMATS: - --http - generate raw *.http files (default) - --curl - generate shell scripts with curl command - --wget - generate shell scripts with wget command + --http - generate raw *.http files (default) + --curl - generate shell scripts with curl command + --wget - generate shell scripts with wget command + --v2.0 - convert from Postman Collection Schema v2.1 into v2.0 + --v2.1 - convert from Postman Collection Schema v2.0 into v2.1 + -a, --all - convert to all of formats listed above If no FORMATS specified then --http implied. -Any of FORMATS can be specified at the same time. - -Notes about variable interpolation: - 1. You can use -e to tell where to find variables to replace in requests. - 2. You can use one or several --var to replace specific env variables to your own value. - 3. Correct syntax is `--var "NAME=VALUE". NAME may be in curly braces like {{NAME}}. - 4. Since -e is optional, a bunch of --var will emulate an environment. Also it does not - matter if there is --var in environment file you provided or not. - 5. Even if you (not) provided -e and/or --var, any of variable may still be overridden - from collection (if any), so last ones has top priority. +Any of FORMATS can be specified at the same time or replaced by --all. Example: - ./pm-convert \ - -f ~/dir1/first.postman_collection.json \ - --directory ~/team \ - --file ~/dir2/second.postman_collection.json \ - --env ~/localhost.postman_environment.json \ - -d ~/personal \ - --var "myvar=some value" \ - -o ~/postman_export - + ./pm-convert \ + -f ~/dir1/first.postman_collection.json \ + --directory ~/team \ + --file ~/dir2/second.postman_collection.json \ + --env ~/localhost.postman_environment.json \ + -d ~/personal \ + --var "myvar=some value" \ + -o ~/postman_export \ + --all ``` ### Notices @@ -116,12 +108,39 @@ Example: If not, you can rename them in Postman or convert collections with similar names into different directories. Otherwise any generated file may be accidently overwritten by another one. +## Notes about variable interpolation + +1. You can use -e to tell where to find variables to replace in requests. +2. You can use one or several --var to replace specific env variables to your own value. +3. Correct syntax is `--var "NAME=VALUE"`. `NAME` may be in curly braces like `{{NAME}}`. +4. Since -e is optional, a bunch of `--var` will emulate an environment. Also it does not matter if there is `--var` in environment file you provided or not. +5. Even if you (not) provided -e and/or `--var`, any of variable may still be overridden from collection (if any), so last ones has top priority. + +### Notes about conversion between Postman Schemas + +You can use `--v2.1` to convert v2.1 into v2.1 (and this is not a typo). +Same applies to `--v2.0`. + +There is a case when a collection has been exported via Postman API. +In such case collection itself places in single root object called `collection` like this: + +``` +{ + "collection": { + // your actual collection here + } +} +``` + +So, pm-convert will just raise actual data up on top level and write into disk. + ## How to implement a new format -1. Create new namespace in `./src/Converters` and name it according to format of your choice -2. Create two classes for converter and request object which extends `Converters\Abstract\Abstract{Converter, Request}` respectively -3. Change constants values in your new request class according to format you want to implement -4. Write your own logic in converter's `__toString()` method, write new methods and override abstract ones +1. Create new namespace in `./src/Converters` and name it according to format of your choice. +2. Create two classes for converter and request object which extends `Converters\Abstract\Abstract{Converter, Request}` respectively. +3. Change constants values in your new request class according to format you want to implement. +4. Add your converter class name in `Converters\ConvertFormat`. +5. Write your own logic in converter, write new methods and override abstract ones. ## License diff --git a/src/Processor.php b/src/Processor.php index 4ddaa2f..0e55292 100644 --- a/src/Processor.php +++ b/src/Processor.php @@ -165,6 +165,13 @@ class Processor $this->formats[ConvertFormat::Postman21->name] = ConvertFormat::Postman21; break; + case '-a': + case '--all': + foreach (ConvertFormat::cases() as $format) { + $this->formats[$format->name] = $format; + } + break; + case '--var': [$var, $value] = explode('=', trim($this->argv[$idx + 1])); $this->vars[$var] = $value; @@ -348,7 +355,6 @@ class Processor "\t-o, --output - a directory OUTPUT_PATH to put results in", "\t-e, --env - use environment file with variables to replace in requests", "\t--var \"NAME=VALUE\" - force replace specified env variable called NAME with custom VALUE", - "\t (see interpolation notes below)", "\t-p, --preserve - do not delete OUTPUT_PATH (if exists)", "\t-h, --help - show this help message and exit", "\t-v, --version - show version info and exit", @@ -361,21 +367,15 @@ class Processor 'If -o or -e was specified several times then only last one will be used.', '', 'Possible FORMATS:', - "\t--http - generate raw *.http files (default)", - "\t--curl - generate shell scripts with curl command", - "\t--wget - generate shell scripts with wget command", + "\t--http - generate raw *.http files (default)", + "\t--curl - generate shell scripts with curl command", + "\t--wget - generate shell scripts with wget command", + "\t--v2.0 - convert from Postman Collection Schema v2.1 into v2.0", + "\t--v2.1 - convert from Postman Collection Schema v2.0 into v2.1", + "\t-a, --all - convert to all of formats listed above", '', 'If no FORMATS specified then --http implied.', - 'Any of FORMATS can be specified at the same time.', - '', - 'Notes about variable interpolation:', - "\t1. You can use -e to tell where to find variables to replace in requests.", - "\t2. You can use one or several --var to replace specific env variables to your own value.", - "\t3. Correct syntax is `--var \"NAME=VALUE\". NAME may be in curly braces like {{NAME}}.", - "\t4. Since -e is optional, a bunch of --var will emulate an environment. Also it does not", - "\t matter if there is --var in environment file you provided or not.", - "\t5. Even if you (not) provided -e and/or --var, any of variable may still be overridden", - "\t from collection (if any), so last ones has top priority.", + 'Any of FORMATS can be specified at the same time or replaced by --all.', '', 'Example:', " ./pm-convert \ ", @@ -385,7 +385,8 @@ class Processor " --env ~/localhost.postman_environment.json \ ", " -d ~/personal \ ", " --var \"myvar=some value\" \ ", - " -o ~/postman_export ", + " -o ~/postman_export \ ", + " --all", "", ], $this->copyright()); } From 771fe4931ab0f22df54f33d71f922e5351817f68 Mon Sep 17 00:00:00 2001 From: Anthony Axenov Date: Mon, 18 Sep 2023 00:40:23 +0800 Subject: [PATCH 5/5] Bump version --- src/Processor.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Processor.php b/src/Processor.php index 0e55292..acd9a29 100644 --- a/src/Processor.php +++ b/src/Processor.php @@ -21,7 +21,7 @@ class Processor /** * Converter version */ - public const VERSION = '1.4.1'; + public const VERSION = '1.5.0'; /** * @var string[] Paths to collection files