Skip to content

Commit 0052902

Browse files
authored
Merge branch 'PG-4396-generate-oa-annotations' into PG-4396-add-workflows
2 parents 29b4efd + 08fbf2b commit 0052902

4 files changed

Lines changed: 102 additions & 15 deletions

File tree

Annotations/AnnotationGenerator.php

Lines changed: 66 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
use PHPStan\PhpDocParser\Parser\TypeParser;
2424
use PHPStan\PhpDocParser\Parser\ConstExprParser;
2525
use PHPStan\PhpDocParser\Parser\TokenIterator;
26+
use function _PHPStan_3d4486d07\RingCentral\Psr7\str;
2627

2728
class AnnotationGenerator
2829
{
@@ -42,19 +43,20 @@ public function __construct(DocumentationGenerator $generator)
4243
* - Uses config.php to set default values.
4344
* - Uses config.php from plugin to override default configs.
4445
*/
45-
public function generatePluginApiAnnotations(string $pluginName)
46+
public function generatePluginApiAnnotations(string $pluginName, bool $writeToFile = false)
4647
{
4748
BaseValidator::check('plugin', $pluginName, [ new NotEmpty() ]);
4849
Manager::getInstance()->checkIsPluginActivated($pluginName);
4950

5051
$currentPluginDir = Manager::getInstance()::getPluginDirectory('OpenApiDocs');
5152
$rules = require $currentPluginDir . '/Annotations/config.php';
5253
$pluginDir = Manager::getInstance()::getPluginDirectory($pluginName);
53-
$pluginConfigPath = $pluginDir . '/OpenApi/Annotations/config.php';
54-
if (is_file($pluginConfigPath)) {
55-
$pluginRules = require $pluginDir . '/OpenApi/Annotations/config.php';
54+
$pluginAnnotationDir = $pluginDir . '/OpenApi/Annotations';
55+
$pluginAnnotationPath = $pluginAnnotationDir . '/GeneratedAnnotations.php';
56+
// If the directory doesn't exist yet, create it
57+
if ($writeToFile && !is_dir($pluginAnnotationDir)) {
58+
mkdir($pluginAnnotationDir, 0777, true);
5659
}
57-
$rules['plugins'] = [ $pluginName => $pluginRules ?? [] ];
5860

5961
$className = Request::getClassNameAPI($pluginName);
6062

@@ -81,13 +83,42 @@ public function generatePluginApiAnnotations(string $pluginName)
8183
$annotations[] = $methodAnnotations;
8284
}
8385

84-
if (empty($annotations)) {
85-
return false;
86+
if ($writeToFile) {
87+
$this->writeAnnotationsToFile($annotations, $pluginAnnotationPath, $pluginName);
8688
}
8789

8890
return $annotations;
8991
}
9092

93+
protected function writeAnnotationsToFile(array $annotations, string $filePath, string $pluginName): void
94+
{
95+
$output = '';
96+
$lines = [
97+
'<?php',
98+
'',
99+
'namespace Piwik\\Plugins\\' . $pluginName . '\\OpenApi\\Annotations;',
100+
'',
101+
'/**',
102+
];
103+
104+
foreach ($annotations as $annotation) {
105+
foreach ($annotation as $line) {
106+
$lines[] = ' * ' . $line;
107+
}
108+
}
109+
110+
$lines = array_merge($lines, [
111+
' */',
112+
'class GeneratedAnnotations',
113+
'{',
114+
'',
115+
'}',
116+
]);
117+
118+
// Create or overwrite the annotations file
119+
file_put_contents($filePath, implode(PHP_EOL, $lines));
120+
}
121+
91122
protected function buildAnnotationForMethod(array $rules, string $pluginName, \ReflectionMethod $reflectionMethod): array
92123
{
93124
$existing = $reflectionMethod->getDocComment();
@@ -270,6 +301,11 @@ protected function getApplicableDemoExampleUrls(string $pluginName, string $meth
270301

271302
protected function getExampleIfAvailable(string $url): array
272303
{
304+
// Simply return the URL for TSV
305+
if (stripos($url, 'format=tsv') !== false) {
306+
return ['externalValue' => $url];
307+
}
308+
273309
$ch = curl_init($url);
274310

275311
curl_setopt_array($ch, [
@@ -284,11 +320,22 @@ protected function getExampleIfAvailable(string $url): array
284320
curl_close($ch);
285321

286322
// If the example didn't load or is too big, simply include the URL instead of the string value
287-
if ($body === false || $status !== 200 || strlen($body) > 1000) {
323+
if ($body === false || $status !== 200 || strlen($body) > 1000 || strpos($body, 'Error: ') === 0) {
288324
return ['externalValue' => $url];
289325
}
290326

291-
return ['value' => trim($body)];
327+
// Clean up XML formatting a bit
328+
$body = trim($body);
329+
if (stripos($url, 'format=xml') !== false) {
330+
$body = str_replace(['<?xml version="1.0" encoding="utf-8" ?>', "\n", "\t", '"'], ['', '', '', '\"'], $body);
331+
}
332+
333+
// The annotation expects an objects and not arrays
334+
if (stripos($url, 'format=json') !== false && stripos($body, '[') === 0) {
335+
$body = str_replace(['[', ']'], ['{', '}'], $body);
336+
}
337+
338+
return ['value' => $body];
292339
}
293340

294341
protected function determineResponses(array $rules, string $plugin, string $method): array
@@ -317,7 +364,13 @@ protected function determineResponses(array $rules, string $plugin, string $meth
317364
'summary="Example ' . $type . '"',
318365
];
319366
$exampleValue = $this->getExampleIfAvailable($url);
320-
$exampleProperties[] = array_key_first($exampleValue) . '="' . array_pop($exampleValue) . '"';
367+
$valueKey = array_key_first($exampleValue);
368+
$value = '"' . array_pop($exampleValue) . '"';
369+
// Remove the surrounding quotes for JSON values
370+
if ($valueKey === 'value' && $type === 'json') {
371+
$value = substr($value, 1, -1);
372+
}
373+
$exampleProperties[] = $valueKey . '=' . $value;
321374
$mediaTypes[] = [
322375
'mediaType="' . $contentType . '"',
323376
'@OA\Examples' => $exampleProperties,
@@ -391,6 +444,9 @@ protected function buildSchemaObjectArray(string $type, string $subType = '', st
391444
}
392445
if ($type === 'array') {
393446
$schemaMap[] = '@OA\Items(' . $subTypeString . ')';
447+
if ($default === '[]') {
448+
$default = '{}';
449+
}
394450
}
395451

396452
if ($default !== '') {

Annotations/config.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
*/
99

1010
return [
11-
'virtualPathTemplate' => '/{plugin}.{method}',
11+
'virtualPathTemplate' => '/index.php?module=API&method={plugin}.{method}',
1212

1313
'defaultParamRefs' => [
1414
'#/components/parameters/formatOptional',

Commands/GenerateAnnotations.php

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,9 +82,13 @@ protected function doExecute(): int
8282

8383
$output->writeln(sprintf('<info>Generating annotations for: %s</info>', $plugin));
8484

85-
// TODO - Add handling for not-dry-run
85+
$result = (StaticContainer::get(AnnotationGenerator::class))->generatePluginApiAnnotations($plugin, $notDryRun);
8686

87-
$result = (StaticContainer::get(AnnotationGenerator::class))->generatePluginApiAnnotations($plugin);
87+
if ($notDryRun) {
88+
$output->writeln('<info>Results written to ' . $plugin . ' plugin\'s /OpenApi/Annotations directory.</info>');
89+
90+
return $result ? self::SUCCESS : self::FAILURE;
91+
}
8892

8993
if (is_array($result)) {
9094
foreach ($result as $annotation) {

Specs/SpecGenerator.php

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@
1313
use OpenApi\Generator;
1414
use Piwik\Container\StaticContainer;
1515
use Piwik\Log\LoggerInterface;
16+
use Piwik\Log\NullLogger;
1617
use Piwik\Plugin\Manager;
18+
use Piwik\Plugins\OpenApiDocs\Annotations\AnnotationGenerator;
1719
use Piwik\SettingsPiwik;
1820
use Piwik\Validators\BaseValidator;
1921
use Piwik\Validators\NotEmpty;
@@ -28,21 +30,46 @@ public function __construct()
2830
}
2931
}
3032

31-
public function generatePluginDoc(string $pluginName, string $format = 'json'): string
33+
public function generatePluginDoc(string $pluginName, string $format = 'json', bool $writeToFile = false): string
3234
{
3335
BaseValidator::check('plugin', $pluginName, [new NotEmpty()]);
3436
Manager::getInstance()->checkIsPluginActivated($pluginName);
3537

3638
$currentPluginDir = Manager::getInstance()::getPluginDirectory('OpenApiDocs');
3739
$pluginDir = Manager::getInstance()::getPluginDirectory($pluginName);
3840

41+
// Check if the API class has been annotated and use the generated annotations file if it hasn't
42+
$pluginAnnotationsSource = $pluginDir . '/API.php';
43+
$openapi = (new Generator(StaticContainer::get(NullLogger::class)))->generate([
44+
$pluginAnnotationsSource,
45+
]);
46+
if (trim($openapi->toYaml()) === 'openapi: ' . OpenApi::DEFAULT_VERSION) {
47+
$pluginAnnotationDir = $pluginDir . '/OpenApi/Annotations';
48+
$pluginAnnotationPath = $pluginAnnotationDir . '/GeneratedAnnotations.php';
49+
$pluginAnnotationsSource = $pluginAnnotationPath;
50+
// If the generated file doesn't exist yet, generate one
51+
if (!is_dir($pluginAnnotationDir) || !file_exists($pluginAnnotationPath)) {
52+
(StaticContainer::get(AnnotationGenerator::class))->generatePluginApiAnnotations($pluginName, true);
53+
}
54+
}
55+
3956
$generator = new Generator(StaticContainer::get(LoggerInterface::class));
4057
$generator->setVersion(OpenApi::DEFAULT_VERSION);
58+
4159
$openapi = $generator->generate([
4260
$currentPluginDir . '/Annotations/GlobalApiComponents.php',
43-
$pluginDir . '/API.php',
61+
$pluginAnnotationsSource,
4462
]);
4563

64+
// Update title with plugin name
65+
$openapi->info->title .= ' for ' . $pluginName . ' plugin';
66+
67+
// Remove the current server so that it isn't used when saving the spec file. It should only leave demo
68+
if ($writeToFile && is_array($openapi->servers) && count($openapi->servers) > 1) {
69+
unset($openapi->servers[0]);
70+
$openapi->servers = array_values($openapi->servers);
71+
}
72+
4673
return strtolower($format) === 'yaml' ? $openapi->toYaml() : $openapi->toJson();
4774
}
4875
}

0 commit comments

Comments
 (0)