v1.5.0 #14

Merged
anthony merged 5 commits from dev into master 2023-09-17 16:42:41 +00:00
11 changed files with 296 additions and 26 deletions
Showing only changes of commit 3c1871ce1f - Show all commits

98
src/Collection.php Normal file
View File

@ -0,0 +1,98 @@
<?php
declare(strict_types = 1);
namespace PmConverter;
use JsonException;
use Stringable;
/**
* Class that describes a request collection
*
* @property array|object $item
* @property object $info
*/
class Collection implements Stringable
{
/**
* Closed constructor so that we could use factory methods
*
* @param object $json
*/
private function __construct(protected object $json)
{
// specific case when collection has been exported via postman api
if (isset($json->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
};
}
}

12
src/CollectionVersion.php Normal file
View File

@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace PmConverter;
enum CollectionVersion: string
{
case Version20 = 'v2.0';
case Version21 = 'v2.1';
case Unknown = 'unknown';
}

View File

@ -5,11 +5,15 @@ declare(strict_types=1);
namespace PmConverter\Converters\Abstract; namespace PmConverter\Converters\Abstract;
use Exception; use Exception;
use PmConverter\Collection;
use PmConverter\Converters\{ use PmConverter\Converters\{
ConverterContract, ConverterContract,
RequestContract}; RequestContract};
use PmConverter\Environment; use PmConverter\Environment;
use PmConverter\Exceptions\InvalidHttpVersionException; use PmConverter\Exceptions\{
CannotCreateDirectoryException,
DirectoryIsNotWriteableException,
InvalidHttpVersionException};
use PmConverter\FileSystem; use PmConverter\FileSystem;
/** /**
@ -18,9 +22,9 @@ use PmConverter\FileSystem;
abstract class AbstractConverter implements ConverterContract abstract class AbstractConverter implements ConverterContract
{ {
/** /**
* @var object|null * @var Collection|null
*/ */
protected ?object $collection = null; protected ?Collection $collection = null;
/** /**
* @var string * @var string
@ -45,14 +49,32 @@ abstract class AbstractConverter implements ConverterContract
} }
/** /**
* Converts collection requests * Creates a new directory to save a converted collection into
* *
* @throws Exception * @param string $outputPath
* @return void
* @throws CannotCreateDirectoryException
* @throws DirectoryIsNotWriteableException
*/ */
public function convert(object $collection, string $outputPath): void protected function prepareOutputDir(string $outputPath): void
{ {
$outputPath = sprintf('%s%s%s', $outputPath, DIRECTORY_SEPARATOR, static::OUTPUT_DIR); $outputPath = sprintf('%s%s%s', $outputPath, DIRECTORY_SEPARATOR, static::OUTPUT_DIR);
$this->outputPath = FileSystem::makeDir($outputPath); $this->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->collection = $collection;
$this->setVariables(); $this->setVariables();
foreach ($collection->item as $item) { foreach ($collection->item as $item) {
@ -94,7 +116,9 @@ abstract class AbstractConverter implements ConverterContract
*/ */
protected function isItemFolder(object $item): bool protected function isItemFolder(object $item): bool
{ {
return !empty($item->item) && empty($item->request); return !empty($item->item)
&& is_array($item->item)
&& empty($item->request);
} }
/** /**

View File

@ -8,7 +8,7 @@ use PmConverter\Converters\RequestContract;
use PmConverter\Exceptions\{ use PmConverter\Exceptions\{
EmptyHttpVerbException, EmptyHttpVerbException,
InvalidHttpVersionException}; InvalidHttpVersionException};
use PmConverter\HttpVersions; use PmConverter\HttpVersion;
use Stringable; use Stringable;
/** /**
@ -61,9 +61,9 @@ abstract class AbstractRequest implements Stringable, RequestContract
*/ */
public function setHttpVersion(float $version): static public function setHttpVersion(float $version): static
{ {
if (!in_array($version, HttpVersions::values())) { if (!in_array($version, HttpVersion::values())) {
throw new InvalidHttpVersionException( throw new InvalidHttpVersionException(
'Only these HTTP versions are supported: ' . implode(', ', HttpVersions::values()) 'Only these HTTP versions are supported: ' . implode(', ', HttpVersion::values())
); );
} }
$this->httpVersion = $version; $this->httpVersion = $version;

View File

@ -8,6 +8,7 @@ namespace PmConverter\Converters;
use PmConverter\Converters\{ use PmConverter\Converters\{
Curl\CurlConverter, Curl\CurlConverter,
Http\HttpConverter, Http\HttpConverter,
Postman20\Postman20Converter,
Wget\WgetConverter}; Wget\WgetConverter};
enum ConvertFormat: string enum ConvertFormat: string
@ -15,4 +16,5 @@ enum ConvertFormat: string
case Http = HttpConverter::class; case Http = HttpConverter::class;
case Curl = CurlConverter::class; case Curl = CurlConverter::class;
case Wget = WgetConverter::class; case Wget = WgetConverter::class;
case Postman20 = Postman20Converter::class;
} }

View File

@ -4,8 +4,10 @@ declare(strict_types=1);
namespace PmConverter\Converters; namespace PmConverter\Converters;
use PmConverter\Collection;
interface ConverterContract interface ConverterContract
{ {
public function convert(object $collection, string $outputPath): void; public function convert(Collection $collection, string $outputPath): void;
public function getOutputPath(): string; public function getOutputPath(): string;
} }

View File

@ -0,0 +1,133 @@
<?php
declare(strict_types=1);
namespace PmConverter\Converters\Postman20;
use PmConverter\Collection;
use PmConverter\Converters\{
Abstract\AbstractConverter,
ConverterContract};
use PmConverter\Exceptions\CannotCreateDirectoryException;
use PmConverter\Exceptions\DirectoryIsNotWriteableException;
use PmConverter\FileSystem;
/**
* Converts Postman Collection v2.1 to v2.0
*/
class Postman20Converter extends AbstractConverter implements ConverterContract
{
protected const FILE_EXT = 'v20.postman_collection.json';
protected const OUTPUT_DIR = 'v2.0';
/**
* Converts collection requests
*
* @param Collection $collection
* @param string $outputPath
* @return void
* @throws CannotCreateDirectoryException
* @throws DirectoryIsNotWriteableException
*/
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);
}
$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;
}
}
}
}

View File

@ -4,6 +4,7 @@ declare(strict_types=1);
namespace PmConverter; namespace PmConverter;
use JsonException;
use PmConverter\Exceptions\{ use PmConverter\Exceptions\{
CannotCreateDirectoryException, CannotCreateDirectoryException,
DirectoryIsNotReadableException, DirectoryIsNotReadableException,
@ -110,16 +111,14 @@ class FileSystem
* *
* @param string $path * @param string $path
* @return bool * @return bool
* @throws JsonException
*/ */
public static function isCollectionFile(string $path): bool public static function isCollectionFile(string $path): bool
{ {
$path = static::normalizePath($path); return (!empty($path = trim(static::normalizePath($path))))
return !empty($path = trim($path))
&& str_ends_with($path, '.postman_collection.json') && str_ends_with($path, '.postman_collection.json')
&& file_exists($path) && file_exists($path)
&& is_readable($path) && is_readable($path)
&& ($json = json_decode(file_get_contents($path), true)) && Collection::fromFile($path)->version() !== CollectionVersion::Unknown;
&& json_last_error() === JSON_ERROR_NONE
&& isset($json['collection']['info']['name']);
} }
} }

View File

@ -4,7 +4,7 @@ declare(strict_types=1);
namespace PmConverter; namespace PmConverter;
enum HttpVersions: string enum HttpVersion: string
{ {
case Version10 = '1.0'; case Version10 = '1.0';
case Version11 = '1.1'; case Version11 = '1.1';

View File

@ -107,7 +107,7 @@ class Processor
$normpath = FileSystem::normalizePath($rawpath); $normpath = FileSystem::normalizePath($rawpath);
if (!FileSystem::isCollectionFile($normpath)) { if (!FileSystem::isCollectionFile($normpath)) {
throw new InvalidArgumentException( 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]; $this->collectionPaths[] = $this->argv[$idx + 1];
@ -156,6 +156,10 @@ class Processor
$this->formats[ConvertFormat::Wget->name] = ConvertFormat::Wget; $this->formats[ConvertFormat::Wget->name] = ConvertFormat::Wget;
break; break;
case '--v2.0':
$this->formats[ConvertFormat::Postman20->name] = ConvertFormat::Postman20;
break;
case '--var': case '--var':
[$var, $value] = explode('=', trim($this->argv[$idx + 1])); [$var, $value] = explode('=', trim($this->argv[$idx + 1]));
$this->vars[$var] = $value; $this->vars[$var] = $value;
@ -219,12 +223,8 @@ class Processor
protected function initCollections(): void protected function initCollections(): void
{ {
foreach ($this->collectionPaths as $collectionPath) { foreach ($this->collectionPaths as $collectionPath) {
$content = file_get_contents(FileSystem::normalizePath($collectionPath)); $collection = Collection::fromFile($collectionPath);
$content = json_decode($content, flags: JSON_THROW_ON_ERROR); $this->collections[$collection->name()] = $collection;
if (!property_exists($content, 'collection') || empty($content?->collection)) {
throw new JsonException("not a valid collection: $collectionPath");
}
$this->collections[$content->collection->info->name] = $content->collection;
} }
unset($this->collectionPaths, $content); unset($this->collectionPaths, $content);
} }

View File

@ -11,7 +11,7 @@ class AbstractRequestTest extends TestCase
{ {
/** /**
* @covers PmConverter\Converters\Abstract\AbstractRequest * @covers PmConverter\Converters\Abstract\AbstractRequest
* @covers PmConverter\HttpVersions * @covers PmConverter\HttpVersion
* @return void * @return void
* @throws InvalidHttpVersionException * @throws InvalidHttpVersionException
*/ */
@ -26,7 +26,7 @@ class AbstractRequestTest extends TestCase
/** /**
* @covers PmConverter\Converters\Abstract\AbstractRequest * @covers PmConverter\Converters\Abstract\AbstractRequest
* @covers PmConverter\Converters\Abstract\AbstractRequest::getVerb() * @covers PmConverter\Converters\Abstract\AbstractRequest::getVerb()
* @covers PmConverter\HttpVersions * @covers PmConverter\HttpVersion
* @return void * @return void
* @throws InvalidHttpVersionException * @throws InvalidHttpVersionException
*/ */