Skip to content

Commit 761cfdb

Browse files
authored
Merge pull request #22 from marcelthole/resolve-9
Add option to disable the resolving of dependencies
2 parents 545b606 + e27f667 commit 761cfdb

25 files changed

+751
-47
lines changed

.editorconfig

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,7 @@ trim_trailing_whitespace = false
1313

1414
[*.{yaml, yml, neon, neon.dist, json, json.dist}]
1515
indent_size = 2
16+
17+
[tests/*/Fixtures/expected/*.json]
18+
indent_size = 4
19+
insert_final_newline = false

.github/workflows/build.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,10 @@ jobs:
6161
- name: "Show dependencies"
6262
run: "composer show"
6363

64+
- name: "Run code style check"
65+
run: "composer run-script cs-check"
66+
if: ${{ matrix.composer-deps == 'latest' && matrix.php-version == '7.4' }}
67+
6468
- name: "Run CI"
6569
run: "composer run-script ci"
6670

bin/openapi-merge

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
use Mthole\OpenApiMerge\Console\Command\MergeCommand;
55
use Mthole\OpenApiMerge\Merge\PathMerger;
66
use Mthole\OpenApiMerge\OpenApiMerge;
7+
use Mthole\OpenApiMerge\Merge\ReferenceNormalizer;
78
use Mthole\OpenApiMerge\Reader\FileReader;
89
use Mthole\OpenApiMerge\Writer\DefinitionWriter;
910
use Symfony\Component\Console\Application;
@@ -24,7 +25,11 @@ use Symfony\Component\Console\Application;
2425

2526
$application = new Application();
2627
$application->add(new MergeCommand(
27-
new OpenApiMerge(new FileReader(), new PathMerger()),
28+
new OpenApiMerge(
29+
new FileReader(),
30+
new PathMerger(),
31+
new ReferenceNormalizer()
32+
),
2833
new DefinitionWriter()
2934
));
3035
$application->setDefaultCommand(MergeCommand::COMMAND_NAME, true);

composer.json

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,16 +38,21 @@
3838
},
3939
"scripts": {
4040
"ci": [
41-
"@cs-check",
4241
"@phpunit",
4342
"@phpstan"
4443
],
4544
"ci-coverage": [
4645
"@phpunit-coverage",
4746
"@infection"
4847
],
49-
"cs-check": "phpcs",
50-
"cs-fix": "phpcbf",
48+
"cs-check": [
49+
"phpcs --config-set php_version 70423",
50+
"phpcs -s"
51+
],
52+
"cs-fix": [
53+
"phpcs --config-set php_version 70423",
54+
"phpcbf"
55+
],
5156
"phpunit": "@php -dzend.assertions=1 ./vendor/bin/phpunit --no-coverage",
5257
"phpunit-coverage": "@php -dxdebug.mode=coverage -dzend.assertions=1 ./vendor/bin/phpunit",
5358
"phpstan": "phpstan analyse",

src/Console/Command/MergeCommand.php

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,13 @@ protected function configure(): void
5454
)
5555
->addArgument('basefile', InputArgument::REQUIRED)
5656
->addArgument('additionalFiles', InputArgument::IS_ARRAY)
57+
->addOption(
58+
'resolve-references',
59+
null,
60+
InputOption::VALUE_OPTIONAL,
61+
'Resolve the "$refs" in the given files',
62+
true
63+
)
5764
->addOption(
5865
'outputfile',
5966
'o',
@@ -71,12 +78,15 @@ protected function execute(InputInterface $input, OutputInterface $output): int
7178
throw new Exception('Invalid arguments given');
7279
}
7380

81+
$shouldResolveReferences = (bool) $input->getOption('resolve-references');
82+
7483
$mergedResult = $this->merger->mergeFiles(
7584
new File($baseFile),
76-
...array_map(
85+
array_map(
7786
static fn (string $file): File => new File($file),
7887
$additionalFiles
79-
)
88+
),
89+
$shouldResolveReferences,
8090
);
8191

8292
$outputFileName = $input->getOption('outputfile');
@@ -88,10 +98,10 @@ protected function execute(InputInterface $input, OutputInterface $output): int
8898
$mergedResult->getOpenApi()
8999
);
90100
file_put_contents(
91-
$outputFile->getAbsolutePath(),
101+
$outputFile->getAbsoluteFile(),
92102
$this->definitionWriter->write($specificationFile)
93103
);
94-
$output->writeln(sprintf('File successfully written to %s', $outputFile->getAbsolutePath()));
104+
$output->writeln(sprintf('File successfully written to %s', $outputFile->getAbsoluteFile()));
95105
} else {
96106
$output->write($this->definitionWriter->write($mergedResult));
97107
}

src/FileHandling/File.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
use Mthole\OpenApiMerge\FileHandling\Exception\IOException;
88

9+
use function dirname;
910
use function getcwd;
1011
use function pathinfo;
1112
use function realpath;
@@ -28,7 +29,7 @@ public function getFileExtension(): string
2829
return pathinfo($this->filename, PATHINFO_EXTENSION);
2930
}
3031

31-
public function getAbsolutePath(): string
32+
public function getAbsoluteFile(): string
3233
{
3334
$fullFilename = realpath($this->filename);
3435
if ($fullFilename === false) {
@@ -40,6 +41,11 @@ public function getAbsolutePath(): string
4041
return $fullFilename;
4142
}
4243

44+
public function getAbsolutePath(): string
45+
{
46+
return dirname($this->getAbsoluteFile());
47+
}
48+
4349
private function createAbsoluteFilePath(string $filename): string
4450
{
4551
if (strpos($filename, '/') === 0) {

src/Merge/ReferenceNormalizer.php

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Mthole\OpenApiMerge\Merge;
6+
7+
use cebe\openapi\spec\MediaType;
8+
use cebe\openapi\spec\OpenApi;
9+
use cebe\openapi\spec\Reference;
10+
use cebe\openapi\spec\Response;
11+
use Mthole\OpenApiMerge\FileHandling\File;
12+
13+
use function array_map;
14+
use function assert;
15+
use function count;
16+
use function preg_match;
17+
18+
use const DIRECTORY_SEPARATOR;
19+
20+
class ReferenceNormalizer
21+
{
22+
public function normalizeInlineReferences(
23+
File $openApiFile,
24+
OpenApi $openApiDefinition
25+
): ReferenceResolverResult {
26+
$refFileCollection = [];
27+
foreach ($openApiDefinition->paths as $path) {
28+
foreach ($path->getOperations() as $operation) {
29+
assert($operation->responses !== null);
30+
foreach ($operation->responses->getResponses() as $statusCode => $response) {
31+
if ($response instanceof Reference) {
32+
$operation->responses->addResponse(
33+
$statusCode,
34+
$this->normalizeReference($response, $refFileCollection)
35+
);
36+
}
37+
38+
if (! ($response instanceof Response)) {
39+
continue;
40+
}
41+
42+
foreach ($response->content as $responseContent) {
43+
assert($responseContent instanceof MediaType);
44+
if ($responseContent->schema instanceof Reference) {
45+
$responseContent->schema = $this->normalizeReference(
46+
$responseContent->schema,
47+
$refFileCollection
48+
);
49+
}
50+
51+
$newExamples = [];
52+
foreach ($responseContent->examples as $key => $example) {
53+
if ($example instanceof Reference) {
54+
$newExamples[$key] = $this->normalizeReference(
55+
$example,
56+
$refFileCollection
57+
);
58+
} elseif ($example !== null) {
59+
$newExamples[$key] = $example;
60+
}
61+
}
62+
63+
if (count($newExamples) <= 0) {
64+
continue;
65+
}
66+
67+
$responseContent->examples = $newExamples;
68+
}
69+
}
70+
}
71+
}
72+
73+
return new ReferenceResolverResult(
74+
$openApiDefinition,
75+
$this->normalizeFilePaths($openApiFile, $refFileCollection)
76+
);
77+
}
78+
79+
/**
80+
* @param array<int, string> $refFileCollection
81+
*/
82+
private function normalizeReference(Reference $reference, array &$refFileCollection): Reference
83+
{
84+
$matches = [];
85+
$referenceFile = $reference->getReference();
86+
if (preg_match('~^(?<referenceFile>.*)(?<referenceString>#/.*)~', $referenceFile, $matches) === 1) {
87+
$refFile = $matches['referenceFile'];
88+
89+
$refFileCollection[] = $refFile;
90+
91+
return new Reference(['$ref' => $matches['referenceString']]);
92+
}
93+
94+
return $reference;
95+
}
96+
97+
/**
98+
* @param array<int, string> $refFileCollection
99+
*
100+
* @return array<int, File>
101+
*/
102+
private function normalizeFilePaths(File $openApiFile, array $refFileCollection): array
103+
{
104+
return array_map(
105+
static fn (string $refFile): File => new File(
106+
$openApiFile->getAbsolutePath() . DIRECTORY_SEPARATOR . $refFile
107+
),
108+
$refFileCollection
109+
);
110+
}
111+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Mthole\OpenApiMerge\Merge;
6+
7+
use cebe\openapi\spec\OpenApi;
8+
use Mthole\OpenApiMerge\FileHandling\File;
9+
10+
final class ReferenceResolverResult
11+
{
12+
private OpenApi $openApiSpecification;
13+
/** @var array<int, File> */
14+
private array $foundReferenceFiles;
15+
16+
/**
17+
* @param array<int, File> $foundReferenceFiles
18+
*/
19+
public function __construct(
20+
OpenApi $openApiSpecification,
21+
array $foundReferenceFiles
22+
) {
23+
$this->openApiSpecification = $openApiSpecification;
24+
$this->foundReferenceFiles = $foundReferenceFiles;
25+
}
26+
27+
public function getNormalizedDefinition(): OpenApi
28+
{
29+
return $this->openApiSpecification;
30+
}
31+
32+
/** @return array<int, File> */
33+
public function getFoundReferenceFiles(): array
34+
{
35+
return $this->foundReferenceFiles;
36+
}
37+
}

src/OpenApiMerge.php

Lines changed: 42 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,38 +4,72 @@
44

55
namespace Mthole\OpenApiMerge;
66

7+
use cebe\openapi\spec\Components;
78
use Mthole\OpenApiMerge\FileHandling\File;
89
use Mthole\OpenApiMerge\FileHandling\SpecificationFile;
910
use Mthole\OpenApiMerge\Merge\PathMergerInterface;
11+
use Mthole\OpenApiMerge\Merge\ReferenceNormalizer;
1012
use Mthole\OpenApiMerge\Reader\FileReader;
1113

14+
use function array_merge;
15+
use function array_push;
16+
use function count;
17+
1218
class OpenApiMerge implements OpenApiMergeInterface
1319
{
1420
private FileReader $openApiReader;
1521

1622
private PathMergerInterface $pathMerger;
23+
private ReferenceNormalizer $referenceNormalizer;
1724

1825
public function __construct(
1926
FileReader $openApiReader,
20-
PathMergerInterface $pathMerger
27+
PathMergerInterface $pathMerger,
28+
ReferenceNormalizer $referenceResolver
2129
) {
22-
$this->openApiReader = $openApiReader;
23-
$this->pathMerger = $pathMerger;
30+
$this->openApiReader = $openApiReader;
31+
$this->pathMerger = $pathMerger;
32+
$this->referenceNormalizer = $referenceResolver;
2433
}
2534

26-
public function mergeFiles(File $baseFile, File ...$additionalFiles): SpecificationFile
35+
/** @param array<int, File> $additionalFiles */
36+
public function mergeFiles(File $baseFile, array $additionalFiles, bool $resolveReference = true): SpecificationFile
2737
{
28-
$mergedOpenApiDefinition = $this->openApiReader->readFile($baseFile)->getOpenApi();
38+
$mergedOpenApiDefinition = $this->openApiReader->readFile($baseFile, $resolveReference)->getOpenApi();
39+
40+
// use "for" instead of "foreach" to iterate over new added files
41+
for ($i = 0; $i < count($additionalFiles); $i++) {
42+
$additionalFile = $additionalFiles[$i];
43+
$additionalDefinition = $this->openApiReader->readFile($additionalFile, $resolveReference)->getOpenApi();
44+
if (! $resolveReference) {
45+
$resolvedReferenceResult = $this->referenceNormalizer->normalizeInlineReferences(
46+
$additionalFile,
47+
$additionalDefinition
48+
);
49+
array_push($additionalFiles, ...$resolvedReferenceResult->getFoundReferenceFiles());
50+
$additionalDefinition = $resolvedReferenceResult->getNormalizedDefinition();
51+
}
2952

30-
foreach ($additionalFiles as $additionalFile) {
31-
$additionalDefinition = $this->openApiReader->readFile($additionalFile)->getOpenApi();
3253
$mergedOpenApiDefinition->paths = $this->pathMerger->mergePaths(
3354
$mergedOpenApiDefinition->paths,
3455
$additionalDefinition->paths
3556
);
57+
58+
if ($additionalDefinition->components === null) {
59+
continue;
60+
}
61+
62+
if ($mergedOpenApiDefinition->components === null) {
63+
$mergedOpenApiDefinition->components = new Components([]);
64+
}
65+
66+
$mergedOpenApiDefinition->components->schemas = array_merge(
67+
$mergedOpenApiDefinition->components->schemas ?? [],
68+
$additionalDefinition->components->schemas ?? []
69+
);
3670
}
3771

38-
if ($mergedOpenApiDefinition->components !== null) {
72+
if ($resolveReference && $mergedOpenApiDefinition->components !== null) {
3973
$mergedOpenApiDefinition->components->schemas = [];
4074
}
4175

src/OpenApiMergeInterface.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,10 @@
99

1010
interface OpenApiMergeInterface
1111
{
12-
public function mergeFiles(File $baseFile, File ...$additionalFiles): SpecificationFile;
12+
/** @param array<int, File> $additionalFiles */
13+
public function mergeFiles(
14+
File $baseFile,
15+
array $additionalFiles,
16+
bool $resolveReference = true
17+
): SpecificationFile;
1318
}

0 commit comments

Comments
 (0)