Skip to content

Commit

Permalink
Merge pull request #4 from veewee/wsdl-tools
Browse files Browse the repository at this point in the history
Introduce WSDL CLI tools
  • Loading branch information
veewee committed Jan 28, 2022
2 parents d12cf90 + 4d0e518 commit 54af50a
Show file tree
Hide file tree
Showing 11 changed files with 517 additions and 6 deletions.
74 changes: 74 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,80 @@ $loader = new FlatteningLoader(new StreamWrapperLoader());
$contents = $loader($wsdl);
```

### CallbackLoader

This loader can be used if you want to have more control about how to load a WSDL.
It can be used to decorate another loader, add debug statements, apply custom loading logic, ...

```php
use Soap\Wsdl\Loader\CallbackLoader;

$loader = new CallbackLoader(static function (string $location) use ($loader, $style): string {
$style->write('> Loading '.$location . '...');

$result = $loader($location);
$style->writeln(' DONE!');

return $result;
})

$contents = $loader($wsdl);
```

## WSDL CLI Tools

```
wsdl-tools 1.0.0
Available commands:
completion Dump the shell completion script
flatten Flatten a remote or local WSDL file into 1 file that contains all includes.
help Display help for a command
list List commands
validate Run validations a (flattened) WSDL file.
```

### Flattening

```
./bin/wsdl flatten 'https://your/?wsdl' out.wsdl
```

This command will download the provided WSDL location.
If any imports are detected, it will download these as well.
The final result is stored in a single WSDL file.

### Validating

```
./bin/wsdl validate out.wsdl
```

This command performs some basic validations on the provided WSDL file.
If your WSDL contains any imports, you'll have to flatten the WSDL into a single file first.

### Custom WSDL Loader

By default, all CLI tools use the StreamWrapperLoader.
All CLI tools have a `--loader=file.php` option that can be used to apply a custom WSDL loader.
This can be handy if your WSDL is located behind authentication or if you want to get control over the HTTP level.

Example custom PHP loader:

```php
<?php

use Soap\Wsdl\Loader\StreamWrapperLoader;

return new StreamWrapperLoader(
stream_context_create([
'http' => [
'method' => 'GET',
'header'=> sprintf('Authorization: Basic %s', base64_encode('username:password')),
],
])
);
```

## WSDL Validators

Expand Down
37 changes: 37 additions & 0 deletions bin/wsdl
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#!/usr/bin/env php
<?php

declare(strict_types=1);

use Soap\Wsdl\Console\AppFactory;

(function () {
$loaded = array_reduce(
[
__DIR__.'/../vendor/autoload.php', // Used when executed in this package's SRC
__DIR__.'/../../../autoload.php' // Used when executed in vendor/bin of your project
],
static function (?string $loaded, string $file): ?string {
if ( ! $loaded && is_file($file)) {
require_once($file);

return $file;
}

return $loaded;
}
);

if (!$loaded) {
fwrite(
STDERR,
'You must set up the project dependencies, run the following commands:'.PHP_EOL.
'curl -s http://getcomposer.org/installer | php'.PHP_EOL.
'php composer.phar install'.PHP_EOL
);
exit(1);
}

$app = AppFactory::create();
$app->run();
})();
10 changes: 9 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,24 @@
"email": "toonverwerft@gmail.com"
}
],
"bin": [
"bin/wsdl"
],
"config": {
"sort-packages": true
},
"require": {
"php": "^8.0",
"ext-dom": "*",
"azjezz/psl": "^1.9",
"league/uri": "^6.5",
"league/uri-components": "^2.4",
"php-soap/xml": "^1.2",
"symfony/console": "^5.4|^6.0",
"veewee/xml": "~1.2"
},
"require-dev": {
"phpunit/phpunit": "^9.5"
"phpunit/phpunit": "^9.5",
"psalm/plugin-symfony": "^3.1"
}
}
13 changes: 8 additions & 5 deletions psalm.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,17 @@
xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
>
<projectFiles>
<directory name="src" />
<directory name="src"/>
<ignoreFiles>
<directory name="vendor" />
<directory name="tests" />
<directory name="vendor"/>
<directory name="tests"/>
</ignoreFiles>
</projectFiles>
<ignoreExceptions>
<class name="InvalidArgumentException" />
<class name="Psl\Exception\InvariantViolationException" />
<class name="InvalidArgumentException"/>
<class name="Psl\Exception\InvariantViolationException"/>
</ignoreExceptions>
<plugins>
<pluginClass class="Psalm\SymfonyPsalmPlugin\Plugin"/>
</plugins>
</psalm>
24 changes: 24 additions & 0 deletions src/Console/AppFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php
declare(strict_types=1);

namespace Soap\Wsdl\Console;

use Symfony\Component\Console\Application;
use Symfony\Component\Console\Exception\LogicException;

final class AppFactory
{
/**
* @throws LogicException
*/
public static function create(): Application
{
$app = new Application('wsdl-tools', '1.0.0');
$app->addCommands([
new Command\FlattenCommand(),
new Command\ValidateCommand(),
]);

return $app;
}
}
77 changes: 77 additions & 0 deletions src/Console/Command/FlattenCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
<?php
declare(strict_types=1);

namespace Soap\Wsdl\Console\Command;

use Exception;
use Psl\Filesystem;
use Soap\Wsdl\Console\Helper\ConfiguredLoader;
use Soap\Wsdl\Loader\CallbackLoader;
use Soap\Wsdl\Loader\FlatteningLoader;
use Soap\Wsdl\Loader\WsdlLoader;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;

final class FlattenCommand extends Command
{
public static function getDefaultName(): string
{
return 'flatten';
}

/**
* @throws InvalidArgumentException
*/
protected function configure(): void
{
$this->setDescription('Flatten a remote or local WSDL file into 1 file that contains all includes.');
$this->addArgument('wsdl', InputArgument::REQUIRED, 'Provide the URI of the WSDL you want to flatten');
$this->addArgument('output', InputArgument::REQUIRED, 'Define where the file must be written to');
$this->addOption('loader', 'l', InputOption::VALUE_REQUIRED, 'Customize the WSDL loader file that will be used');
}

/**
* @throws Exception
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$style = new SymfonyStyle($input, $output);
$loader = ConfiguredLoader::createFromConfig(
$input->getOption('loader'),
fn (WsdlLoader $loader) => $this->configureLoader($loader, $style)
);
$wsdl = $input->getArgument('wsdl');
$output = $input->getArgument('output');

$style->info('Flattening WSDL "'.$wsdl.'"');
$style->warning('This can take a while...');
$contents = $loader($wsdl);

$style->info('Downloaded the WSDL. Writing it to "'.$output.'".');

Filesystem\write_file($output, $contents);

$style->success('Succesfully flattened your WSDL!');

return self::SUCCESS;
}

private function configureLoader(WsdlLoader $loader, SymfonyStyle $style): WsdlLoader
{
return new FlatteningLoader(
new CallbackLoader(static function (string $location) use ($loader, $style): string {
$style->write('> Loading '.$location . '...');

$result = $loader($location);
$style->writeln(' DONE!');

return $result;
})
);
}
}
95 changes: 95 additions & 0 deletions src/Console/Command/ValidateCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
<?php
declare(strict_types=1);

namespace Soap\Wsdl\Console\Command;

use Exception;
use Soap\Wsdl\Console\Helper\ConfiguredLoader;
use Soap\Wsdl\Xml\Validator;
use Soap\Xml\Xpath\WsdlPreset;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use VeeWee\Xml\Dom\Document;
use VeeWee\Xml\ErrorHandling\Issue\IssueCollection;

final class ValidateCommand extends Command
{
public static function getDefaultName(): string
{
return 'validate';
}

/**
* @throws InvalidArgumentException
*/
protected function configure(): void
{
$this->setDescription('Run validations a (flattened) WSDL file.');
$this->addArgument('wsdl', InputArgument::REQUIRED, 'Provide the URI of the WSDL you want to validate');
$this->addOption('loader', 'l', InputOption::VALUE_REQUIRED, 'Customize the WSDL loader file that will be used');
}

/**
* @throws Exception
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$style = new SymfonyStyle($input, $output);
$loader = ConfiguredLoader::createFromConfig($input->getOption('loader'));
$wsdl = $input->getArgument('wsdl');

$style->info('Loading "'.$wsdl.'"...');
$document = Document::fromXmlString($loader($wsdl));
$xpath = $document->xpath(new WsdlPreset($document));

$result = $this->runValidationStage(
$style,
'Validating WSDL syntax',
static fn () => $document->validate(new Validator\WsdlSyntaxValidator())
);

$result = $result && $this->runValidationStage(
$style,
'Validating XSD types...',
static function () use ($style, $document, $xpath): ?IssueCollection {
$schemas = $xpath->query('//schema:schema');
if ($schemas->count() !== 1) {
$style->warning('Skipped : XSD types can only be validated if there is one schema element.');
return null;
}

return $document->validate(new Validator\SchemaSyntaxValidator());
}
);

return $result ? self::SUCCESS : self::FAILURE;
}

/**
* @param callable(): ?IssueCollection $validator
*/
private function runValidationStage(SymfonyStyle $style, string $label, callable $validator): bool
{
$style->info($label.'...');
$issues = $validator();

// Skipped ...
if ($issues === null) {
return true;
}

if ($issues->count()) {
$style->block($issues->toString());
$style->error('Validation failed!');
return false;
}

$style->success('All good!');
return true;
}
}
Loading

0 comments on commit 54af50a

Please sign in to comment.