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 */