Skip to content

Commit

Permalink
Add PathFilter processor to filter paths (zircote#1613)
Browse files Browse the repository at this point in the history
  • Loading branch information
DerManoMann committed Jul 2, 2024
1 parent 5eb7a90 commit b308ec0
Show file tree
Hide file tree
Showing 8 changed files with 235 additions and 58 deletions.
17 changes: 17 additions & 0 deletions src/Annotations/PathItem.php
Original file line number Diff line number Diff line change
Expand Up @@ -155,4 +155,21 @@ class PathItem extends AbstractAnnotation
public static $_parents = [
OpenApi::class,
];

/**
* Returns a list of all operations (all methods) for this path item.
*
* @return Operation[]
*/
public function operations(): array
{
$operations = [];
foreach (PathItem::$_nested as $className => $property) {
if (is_subclass_of($className, Operation::class) && !Generator::isDefault($this->{$property})) {
$operations[] = $this->{$property};
}
}

return $operations;
}
}
2 changes: 2 additions & 0 deletions src/Generator.php
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,8 @@ public function getProcessorPipeline(): Pipeline
new Processors\OperationId(),
new Processors\AugmentTags(),
new Processors\CleanUnmerged(),
new Processors\PathFilter(),
new Processors\CleanUnusedComponents(),
]);
}

Expand Down
4 changes: 3 additions & 1 deletion src/Processors/AugmentParameters.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,11 @@ public function isAugmentOperationParameters(): bool
/**
* If set to <code>true</code> try to find operation parameter descriptions in the operation docblock.
*/
public function setAugmentOperationParameters(bool $augmentOperationParameters): void
public function setAugmentOperationParameters(bool $augmentOperationParameters): AugmentParameters
{
$this->augmentOperationParameters = $augmentOperationParameters;

return $this;
}

public function __invoke(Analysis $analysis)
Expand Down
39 changes: 33 additions & 6 deletions src/Processors/CleanUnusedComponents.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,42 @@
use OpenApi\Annotations as OA;
use OpenApi\Generator;

/**
* Tracks the use of all <code>Components</code> and removed unused schemas.
*/
class CleanUnusedComponents implements ProcessorInterface
{
use Concerns\CollectorTrait;
use Concerns\AnnotationTrait;

/**
* @var bool
*/
protected $enabled = false;

public function __construct(bool $enabled = false)
{
$this->enabled = $enabled;
}

public function isEnabled(): bool
{
return $this->enabled;
}

public function setEnabled(bool $enabled): CleanUnusedComponents
{
$this->enabled = $enabled;

return $this;
}

public function __invoke(Analysis $analysis)
{
if (Generator::isDefault($analysis->openapi->components)) {
if (!$this->enabled || Generator::isDefault($analysis->openapi->components)) {
return;
}

$analysis->annotations = $this->collect($analysis->annotations);
$analysis->annotations = $this->collectAnnotations($analysis->annotations);

// allow multiple runs to catch nested dependencies
for ($ii = 0; $ii < 10; ++$ii) {
Expand Down Expand Up @@ -83,10 +108,12 @@ protected function cleanup(Analysis $analysis): bool
foreach ($analysis->openapi->components->{$componentType} as $ii => $component) {
if ($component->{$nameProperty} == $name) {
$annotation = $analysis->openapi->components->{$componentType}[$ii];
foreach ($this->collect([$annotation]) as $unused) {
$analysis->annotations->detach($unused);
}
$this->removeAnnotation($analysis->annotations, $annotation);
unset($analysis->openapi->components->{$componentType}[$ii]);

if (!$analysis->openapi->components->{$componentType}) {
$analysis->openapi->components->{$componentType} = Generator::UNDEFINED;
}
}
}
}
Expand Down
67 changes: 67 additions & 0 deletions src/Processors/Concerns/AnnotationTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<?php declare(strict_types=1);

/**
* @license Apache 2.0
*/

namespace OpenApi\Processors\Concerns;

use OpenApi\Annotations as OA;

trait AnnotationTrait
{
/**
* Collects a (complete) list of all nested/referenced annotations starting from the given root.
*
* @param string|array|iterable|OA\AbstractAnnotation $root
*/
public function collectAnnotations($root): \SplObjectStorage
{
$storage = new \SplObjectStorage();

$this->traverseAnnotations($root, function ($item) use (&$storage) {
if ($item instanceof OA\AbstractAnnotation && !$storage->contains($item)) {
$storage->attach($item);
}
});

return $storage;
}

/**
* Remove all annotations that are part of the `$annotation` tree.
*/
public function removeAnnotation(iterable $root, OA\AbstractAnnotation $annotation): void
{
$remove = $this->collectAnnotations($annotation);
$this->traverseAnnotations($root, function ($item) use ($remove) {
if ($item instanceof \SplObjectStorage) {
foreach ($remove as $annotation) {
$item->detach($annotation);
}
}
});
}

/**
* @param string|array|iterable|OA\AbstractAnnotation $root
*/
public function traverseAnnotations($root, callable $callable): void
{
$callable($root);

if (is_iterable($root)) {
foreach ($root as $value) {
$this->traverseAnnotations($value, $callable);
}
} elseif ($root instanceof OA\AbstractAnnotation) {
foreach (array_merge($root::$_nested, ['allOf', 'anyOf', 'oneOf', 'callbacks']) as $properties) {
foreach ((array) $properties as $property) {
if (isset($root->{$property})) {
$this->traverseAnnotations($root->{$property}, $callable);
}
}
}
}
}
}
48 changes: 0 additions & 48 deletions src/Processors/Concerns/CollectorTrait.php

This file was deleted.

105 changes: 105 additions & 0 deletions src/Processors/PathFilter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
<?php declare(strict_types=1);

/**
* @license Apache 2.0
*/

namespace OpenApi\Processors;

use OpenApi\Analysis;
use OpenApi\Generator;
use OpenApi\Processors\Concerns\AnnotationTrait;

/**
* Allows to filter endpoints based on tags and/or path.
*
* If no `tags` or `paths` filters are set, no filtering is performed.
* All filter (regular) expressions must be enclosed within delimiter characters as they are used as-is.
*/
class PathFilter implements ProcessorInterface
{
use AnnotationTrait;

/** @var array */
protected $tags = [];

/** @var array */
protected $paths = [];

public function __construct(array $tags = [], array $paths = [])
{
$this->tags = $tags;
$this->paths = $paths;
}

public function getTags(): array
{
return $this->tags;
}

/**
* A list of regular expressions to match <code>tags</code> to include.
*
* @param array<string> $tags
*/
public function setTags(array $tags): PathFilter
{
$this->tags = $tags;

return $this;
}

public function getPaths(): array
{
return $this->paths;
}

/**
* A list of regular expressions to match <code>paths</code> to include.
*
* @param array<string> $paths
*/
public function setPaths(array $paths): PathFilter
{
$this->paths = $paths;

return $this;
}

public function __invoke(Analysis $analysis)
{
if (($this->tags || $this->paths) && !Generator::isDefault($analysis->openapi->paths)) {
$filtered = [];
foreach ($analysis->openapi->paths as $pathItem) {
$matched = null;
foreach ($this->tags as $pattern) {
foreach ($pathItem->operations() as $operation) {
if (!Generator::isDefault($operation->tags)) {
foreach ($operation->tags as $tag) {
if (preg_match($pattern, $tag)) {
$matched = $pathItem;
break 3;
}
}
}
}
}

foreach ($this->paths as $pattern) {
if (preg_match($pattern, $pathItem->path)) {
$matched = $pathItem;
break;
}
}

if ($matched) {
$filtered[] = $matched;
} else {
$this->removeAnnotation($analysis->annotations, $pathItem);
}
}

$analysis->openapi->paths = $filtered;
}
}
}
11 changes: 8 additions & 3 deletions tests/Processors/CleanUnusedComponentsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

namespace OpenApi\Tests\Processors;

use OpenApi\Generator;
use OpenApi\Processors\CleanUnusedComponents;
use OpenApi\Tests\OpenApiTestCase;

Expand All @@ -17,9 +18,9 @@ public static function countCases(): iterable

return [
'var-default' => [$defaultProcessors, 'UsingVar.php', 2, 5],
'var-clean' => [array_merge($defaultProcessors, [new CleanUnusedComponents()]), 'UsingVar.php', 0, 2],
'var-clean' => [array_merge($defaultProcessors, [new CleanUnusedComponents(true)]), 'UsingVar.php', 0, 2],
'unreferenced-default' => [$defaultProcessors, 'Unreferenced.php', 2, 11],
'unreferenced-clean' => [array_merge($defaultProcessors, [new CleanUnusedComponents()]), 'Unreferenced.php', 0, 5],
'unreferenced-clean' => [array_merge($defaultProcessors, [new CleanUnusedComponents(true)]), 'Unreferenced.php', 0, 5],
];
}

Expand All @@ -30,7 +31,11 @@ public function testCounts(array $processors, string $fixture, int $expectedSche
{
$analysis = $this->analysisFromFixtures([$fixture], $processors);

$this->assertCount($expectedSchemaCount, $analysis->openapi->components->schemas);
if ($expectedSchemaCount === 0) {
$this->assertTrue(Generator::isDefault($analysis->openapi->components->schemas));
} else {
$this->assertCount($expectedSchemaCount, $analysis->openapi->components->schemas);
}
$this->assertCount($expectedAnnotationCount, $analysis->annotations);
}
}

0 comments on commit b308ec0

Please sign in to comment.