v1.5.0 #14
57
README.md
57
README.md
@ -24,10 +24,9 @@ These formats are supported for now: `http`, `curl`, `wget`.
|
|||||||
|
|
||||||
## Planned features
|
## 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 authentication kinds (_currently only `Bearer` supported_);
|
||||||
- support as many as possible/necessary of body formats (_currently only `json` and `formdata`_);
|
- 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...);
|
- maybe some another convert formats (like httpie or something...);
|
||||||
- better logging;
|
- better logging;
|
||||||
- 90%+ test coverage, phpcs, psalm, etc.;
|
- 90%+ test coverage, phpcs, psalm, etc.;
|
||||||
@ -65,7 +64,6 @@ Possible ARGUMENTS:
|
|||||||
-o, --output - a directory OUTPUT_PATH to put results in
|
-o, --output - a directory OUTPUT_PATH to put results in
|
||||||
-e, --env - use environment file with variables to replace in requests
|
-e, --env - use environment file with variables to replace in requests
|
||||||
--var "NAME=VALUE" - force replace specified env variable called NAME with custom VALUE
|
--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)
|
-p, --preserve - do not delete OUTPUT_PATH (if exists)
|
||||||
-h, --help - show this help message and exit
|
-h, --help - show this help message and exit
|
||||||
-v, --version - show version info and exit
|
-v, --version - show version info and exit
|
||||||
@ -81,18 +79,12 @@ Possible FORMATS:
|
|||||||
--http - generate raw *.http files (default)
|
--http - generate raw *.http files (default)
|
||||||
--curl - generate shell scripts with curl command
|
--curl - generate shell scripts with curl command
|
||||||
--wget - generate shell scripts with wget 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.
|
If no FORMATS specified then --http implied.
|
||||||
Any of FORMATS can be specified at the same time.
|
Any of FORMATS can be specified at the same time or replaced by --all.
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
./pm-convert \
|
./pm-convert \
|
||||||
@ -102,8 +94,8 @@ Example:
|
|||||||
--env ~/localhost.postman_environment.json \
|
--env ~/localhost.postman_environment.json \
|
||||||
-d ~/personal \
|
-d ~/personal \
|
||||||
--var "myvar=some value" \
|
--var "myvar=some value" \
|
||||||
-o ~/postman_export
|
-o ~/postman_export \
|
||||||
|
--all
|
||||||
```
|
```
|
||||||
|
|
||||||
### Notices
|
### Notices
|
||||||
@ -116,12 +108,39 @@ Example:
|
|||||||
If not, you can rename them in Postman or convert collections with similar names into different directories.
|
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.
|
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
|
## How to implement a new format
|
||||||
|
|
||||||
1. Create new namespace in `./src/Converters` and name it according to format of your choice
|
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
|
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
|
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
|
4. Add your converter class name in `Converters\ConvertFormat`.
|
||||||
|
5. Write your own logic in converter, write new methods and override abstract ones.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
@ -13,7 +13,8 @@
|
|||||||
"keywords": ["postman", "collection", "converter", "http", "wget", "curl", "api", "convert"],
|
"keywords": ["postman", "collection", "converter", "http", "wget", "curl", "api", "convert"],
|
||||||
"require": {
|
"require": {
|
||||||
"php": "^8.1",
|
"php": "^8.1",
|
||||||
"ext-json": "*"
|
"ext-json": "*",
|
||||||
|
"ext-mbstring": "*"
|
||||||
},
|
},
|
||||||
"bin": ["pm-convert"],
|
"bin": ["pm-convert"],
|
||||||
"autoload": {
|
"autoload": {
|
||||||
|
99
src/Collection.php
Normal file
99
src/Collection.php
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types = 1);
|
||||||
|
|
||||||
|
namespace PmConverter;
|
||||||
|
|
||||||
|
use JsonException;
|
||||||
|
use Stringable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class that describes a request collection
|
||||||
|
*
|
||||||
|
* @property array|object $item
|
||||||
|
* @property object $info
|
||||||
|
* @property object|null $variable
|
||||||
|
*/
|
||||||
|
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
12
src/CollectionVersion.php
Normal 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';
|
||||||
|
}
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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;
|
||||||
|
@ -8,6 +8,8 @@ namespace PmConverter\Converters;
|
|||||||
use PmConverter\Converters\{
|
use PmConverter\Converters\{
|
||||||
Curl\CurlConverter,
|
Curl\CurlConverter,
|
||||||
Http\HttpConverter,
|
Http\HttpConverter,
|
||||||
|
Postman20\Postman20Converter,
|
||||||
|
Postman21\Postman21Converter,
|
||||||
Wget\WgetConverter};
|
Wget\WgetConverter};
|
||||||
|
|
||||||
enum ConvertFormat: string
|
enum ConvertFormat: string
|
||||||
@ -15,4 +17,6 @@ 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;
|
||||||
|
case Postman21 = Postman21Converter::class;
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
137
src/Converters/Postman20/Postman20Converter.php
Normal file
137
src/Converters/Postman20/Postman20Converter.php
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace PmConverter\Converters\Postman20;
|
||||||
|
|
||||||
|
use PmConverter\Collection;
|
||||||
|
use PmConverter\CollectionVersion;
|
||||||
|
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 = 'pm-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;
|
||||||
|
// 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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)) {
|
||||||
|
$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)) {
|
||||||
|
$response->originalRequest->url = $response->originalRequest->url->raw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
169
src/Converters/Postman21/Postman21Converter.php
Normal file
169
src/Converters/Postman21/Postman21Converter.php
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace PmConverter\Converters\Postman21;
|
||||||
|
|
||||||
|
use PmConverter\Collection;
|
||||||
|
use PmConverter\CollectionVersion;
|
||||||
|
use PmConverter\Converters\{
|
||||||
|
Abstract\AbstractConverter,
|
||||||
|
ConverterContract};
|
||||||
|
use PmConverter\Exceptions\CannotCreateDirectoryException;
|
||||||
|
use PmConverter\Exceptions\DirectoryIsNotWriteableException;
|
||||||
|
use PmConverter\FileSystem;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts Postman Collection v2.0 to v2.1
|
||||||
|
*/
|
||||||
|
class Postman21Converter extends AbstractConverter implements ConverterContract
|
||||||
|
{
|
||||||
|
protected const FILE_EXT = 'v21.postman_collection.json';
|
||||||
|
|
||||||
|
protected const OUTPUT_DIR = 'pm-v2.1';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
// 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -12,14 +12,16 @@ class Environment implements \ArrayAccess
|
|||||||
protected array $vars = [];
|
protected array $vars = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param object $env
|
* @param object|null $env
|
||||||
*/
|
*/
|
||||||
public function __construct(protected object $env)
|
public function __construct(protected ?object $env)
|
||||||
{
|
{
|
||||||
|
if (!empty($env->values)) {
|
||||||
foreach ($env->values as $var) {
|
foreach ($env->values as $var) {
|
||||||
$this->vars[static::formatKey($var->key)] = $var->value;
|
$this->vars[static::formatKey($var->key)] = $var->value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tells if there are some vars or not
|
* Tells if there are some vars or not
|
||||||
|
@ -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']);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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';
|
@ -21,7 +21,7 @@ class Processor
|
|||||||
/**
|
/**
|
||||||
* Converter version
|
* Converter version
|
||||||
*/
|
*/
|
||||||
public const VERSION = '1.4.1';
|
public const VERSION = '1.5.0';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var string[] Paths to collection files
|
* @var string[] Paths to collection files
|
||||||
@ -54,7 +54,7 @@ class Processor
|
|||||||
protected array $converters = [];
|
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 = [];
|
protected array $collections = [];
|
||||||
|
|
||||||
@ -93,6 +93,7 @@ class Processor
|
|||||||
* Parses an array of arguments came from cli
|
* Parses an array of arguments came from cli
|
||||||
*
|
*
|
||||||
* @return void
|
* @return void
|
||||||
|
* @throws JsonException
|
||||||
*/
|
*/
|
||||||
protected function parseArgs(): void
|
protected function parseArgs(): void
|
||||||
{
|
{
|
||||||
@ -107,7 +108,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 +157,21 @@ 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 '--v2.1':
|
||||||
|
$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':
|
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 +235,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);
|
||||||
}
|
}
|
||||||
@ -297,8 +309,13 @@ class Processor
|
|||||||
protected function printStats(int $success, int $count): void
|
protected function printStats(int $success, int $count): void
|
||||||
{
|
{
|
||||||
$time = (hrtime(true) - $this->initTime) / 1_000_000;
|
$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;
|
$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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -338,7 +355,6 @@ class Processor
|
|||||||
"\t-o, --output - a directory OUTPUT_PATH to put results in",
|
"\t-o, --output - a directory OUTPUT_PATH to put results in",
|
||||||
"\t-e, --env - use environment file with variables to replace in requests",
|
"\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--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-p, --preserve - do not delete OUTPUT_PATH (if exists)",
|
||||||
"\t-h, --help - show this help message and exit",
|
"\t-h, --help - show this help message and exit",
|
||||||
"\t-v, --version - show version info and exit",
|
"\t-v, --version - show version info and exit",
|
||||||
@ -354,18 +370,12 @@ class Processor
|
|||||||
"\t--http - generate raw *.http files (default)",
|
"\t--http - generate raw *.http files (default)",
|
||||||
"\t--curl - generate shell scripts with curl command",
|
"\t--curl - generate shell scripts with curl command",
|
||||||
"\t--wget - generate shell scripts with wget 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.',
|
'If no FORMATS specified then --http implied.',
|
||||||
'Any of FORMATS can be specified at the same time.',
|
'Any of FORMATS can be specified at the same time or replaced by --all.',
|
||||||
'',
|
|
||||||
'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.",
|
|
||||||
'',
|
'',
|
||||||
'Example:',
|
'Example:',
|
||||||
" ./pm-convert \ ",
|
" ./pm-convert \ ",
|
||||||
@ -375,7 +385,8 @@ class Processor
|
|||||||
" --env ~/localhost.postman_environment.json \ ",
|
" --env ~/localhost.postman_environment.json \ ",
|
||||||
" -d ~/personal \ ",
|
" -d ~/personal \ ",
|
||||||
" --var \"myvar=some value\" \ ",
|
" --var \"myvar=some value\" \ ",
|
||||||
" -o ~/postman_export ",
|
" -o ~/postman_export \ ",
|
||||||
|
" --all",
|
||||||
"",
|
"",
|
||||||
], $this->copyright());
|
], $this->copyright());
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
*/
|
*/
|
||||||
|
Loading…
Reference in New Issue
Block a user