diff --git a/README.md b/README.md index ccf544b..761addf 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,8 @@ Version < 1.2 requires zircote/swagger-php 2.x which works with the OpenAPI Spec So tobion/openapi-symfony-routing can be used with both OpenAPI v2 and v3 and composer will select the compatible one for your dependencies. Route loading stays the same between those versions. You just need to update the annotations when migrating from OpenAPI v2 to v3. +Version >= 1.3 requires zircote/swagger-php >=4.1 which is compatible with the OpenAPI Specification version 3.0.1 and supports attributes. This package need php >= 8.1 for attributes support from zircote/swagger-php. + ## Basic Usage This library allows to (re-)use your OpenAPI documentation to configure the routing of your Symfony-based API. diff --git a/add_composer.sh b/add_composer.sh new file mode 100644 index 0000000..3dc47eb --- /dev/null +++ b/add_composer.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash + +export COMPOSER_HOME=/tmp/composer + +php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" +php -r "if (hash_file('sha384', 'composer-setup.php') === '906a84df04cea2aa72f40b5f787e49f22d4c2f19492ac310e8cba5b96ac8b64115ac402c8cd292b8a03482574915d1a8') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;" +php composer-setup.php +php -r "unlink('composer-setup.php');" +chmod +x composer.phar +mv composer.phar composer diff --git a/composer.json b/composer.json index 7d211ef..0fcaf40 100644 --- a/composer.json +++ b/composer.json @@ -16,7 +16,7 @@ "symfony/finder": "^4.4|^5.0|^6.0", "symfony/framework-bundle": "^4.4|^5.0|^6.0", "symfony/routing": "^4.4|^5.0|^6.0", - "zircote/swagger-php": "^3.0.3" + "zircote/swagger-php": "^3.0.3|^4.1" }, "require-dev": { "symfony/phpunit-bridge": "^5.2|^6.0" diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..e65ec00 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,20 @@ +services: + php7: + image: php:7.2-fpm + user: "1000:1000" + working_dir: /var/www/html + volumes: + - ./:/var/www/html + php8.0: + image: php:8.0-fpm + user: "1000:1000" + working_dir: /var/www/html + volumes: + - ./:/var/www/html + php8.1: + image: php:8.1-fpm + working_dir: /var/www/html + user: "1000:1000" + volumes: + - ./:/var/www/html + diff --git a/src/OpenApiRouteLoader.php b/src/OpenApiRouteLoader.php index ad6deb4..67622c4 100644 --- a/src/OpenApiRouteLoader.php +++ b/src/OpenApiRouteLoader.php @@ -27,6 +27,11 @@ class OpenApiRouteLoader implements RouteLoaderInterface */ private $routeNames = []; + /** + * @var null|Generator + */ + private $generator = null; + /** * @var string */ @@ -86,12 +91,30 @@ private function createOpenApi(): OpenApi return \OpenApi\scan($this->finder); } - $processors = array_filter(Analysis::processors(), static function ($processor): bool { - // remove OperationId processor which would hash the controller starting in 3.2.2 breaking the default route name logic - return !$processor instanceof OperationId && !$processor instanceof DocBlockDescriptions; - }); + if (null !== $this->generator) { + return $this->generator->generate($this->finder); + } + + if (method_exists(Analysis::class, 'processors')) { + $processors = array_filter(Analysis::processors(), static function ($processor): bool { + // remove OperationId processor which would hash the controller starting in 3.2.2 breaking the default route name logic + return !$processor instanceof OperationId && !$processor instanceof DocBlockDescriptions; + }); + + $this->generator = (new Generator())->setProcessors($this->filterProcessors($processors)); + + return $this->generator->generate($this->finder); + } + + $this->generator = new Generator(); - return (new Generator())->setProcessors($processors)->generate($this->finder); + $this->generator->setProcessors( + $this->filterProcessors( + $this->generator->getProcessors() + ) + ); + + return $this->generator->generate($this->finder); } /** @@ -128,8 +151,14 @@ private function createRoute(Operation $operation, string $controller, FormatSuf } if (self::$openApiUndefined !== $operation->parameters) { foreach ($operation->parameters as $parameter) { - if ('path' === $parameter->in && self::$openApiUndefined !== $parameter->schema && self::$openApiUndefined !== $parameter->schema->pattern) { - $route->setRequirement($parameter->name, $parameter->schema->pattern); + if ('path' === $parameter->in && self::$openApiUndefined !== $parameter->schema) { + if (self::$openApiUndefined !== $parameter->schema->pattern) { + $route->setRequirement($parameter->name, $parameter->schema->pattern); + } + + if (self::$openApiUndefined !== $parameter->schema->enum) { + $route->setRequirement($parameter->name, implode('|', $parameter->schema->enum)); + } } } } @@ -190,4 +219,11 @@ private function getDefaultRouteName(string $controller): string return $name; } + + private function filterProcessors(array $processors): array + { + return array_filter($processors, static function ($processor): bool { + return !$processor instanceof OperationId && !$processor instanceof DocBlockDescriptions; + }); + } } diff --git a/tests/Fixtures/Basic/Controller.php b/tests/Annotations/Fixtures/Basic/Controller.php similarity index 82% rename from tests/Fixtures/Basic/Controller.php rename to tests/Annotations/Fixtures/Basic/Controller.php index bb8aa6b..c9bbdb7 100644 --- a/tests/Fixtures/Basic/Controller.php +++ b/tests/Annotations/Fixtures/Basic/Controller.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Tobion\OpenApiSymfonyRouting\Tests\Fixtures\Basic; +namespace Tobion\OpenApiSymfonyRouting\Tests\Annotations\Fixtures\Basic; use OpenApi\Annotations as OA; diff --git a/tests/Fixtures/FormatSuffix/Controller.php b/tests/Annotations/Fixtures/FormatSuffix/Controller.php similarity index 92% rename from tests/Fixtures/FormatSuffix/Controller.php rename to tests/Annotations/Fixtures/FormatSuffix/Controller.php index d08b11a..776dd8e 100644 --- a/tests/Fixtures/FormatSuffix/Controller.php +++ b/tests/Annotations/Fixtures/FormatSuffix/Controller.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Tobion\OpenApiSymfonyRouting\Tests\Fixtures\FormatSuffix; +namespace Tobion\OpenApiSymfonyRouting\Tests\Annotations\Fixtures\FormatSuffix; use OpenApi\Annotations as OA; diff --git a/tests/Fixtures/OperationId/Controller.php b/tests/Annotations/Fixtures/OperationId/Controller.php similarity index 82% rename from tests/Fixtures/OperationId/Controller.php rename to tests/Annotations/Fixtures/OperationId/Controller.php index 3784059..be63143 100644 --- a/tests/Fixtures/OperationId/Controller.php +++ b/tests/Annotations/Fixtures/OperationId/Controller.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Tobion\OpenApiSymfonyRouting\Tests\Fixtures\OperationId; +namespace Tobion\OpenApiSymfonyRouting\Tests\Annotations\Fixtures\OperationId; use OpenApi\Annotations as OA; diff --git a/tests/Fixtures/PathParameterPattern/Controller.php b/tests/Annotations/Fixtures/PathParameterPattern/Controller.php similarity index 93% rename from tests/Fixtures/PathParameterPattern/Controller.php rename to tests/Annotations/Fixtures/PathParameterPattern/Controller.php index 101bcc3..c175881 100644 --- a/tests/Fixtures/PathParameterPattern/Controller.php +++ b/tests/Annotations/Fixtures/PathParameterPattern/Controller.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Tobion\OpenApiSymfonyRouting\Tests\Fixtures\PathParameterPattern; +namespace Tobion\OpenApiSymfonyRouting\Tests\Annotations\Fixtures\PathParameterPattern; use OpenApi\Annotations as OA; diff --git a/tests/Fixtures/Priority/Controller.php b/tests/Annotations/Fixtures/Priority/Controller.php similarity index 90% rename from tests/Fixtures/Priority/Controller.php rename to tests/Annotations/Fixtures/Priority/Controller.php index 9876c69..e93790a 100644 --- a/tests/Fixtures/Priority/Controller.php +++ b/tests/Annotations/Fixtures/Priority/Controller.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Tobion\OpenApiSymfonyRouting\Tests\Fixtures\Priority; +namespace Tobion\OpenApiSymfonyRouting\Tests\Annotations\Fixtures\Priority; use OpenApi\Annotations as OA; diff --git a/tests/Fixtures/SeveralClasses/BarController.php b/tests/Annotations/Fixtures/SeveralClasses/BarController.php similarity index 80% rename from tests/Fixtures/SeveralClasses/BarController.php rename to tests/Annotations/Fixtures/SeveralClasses/BarController.php index cfc7b25..e8c0e14 100644 --- a/tests/Fixtures/SeveralClasses/BarController.php +++ b/tests/Annotations/Fixtures/SeveralClasses/BarController.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Tobion\OpenApiSymfonyRouting\Tests\Fixtures\SeveralClasses; +namespace Tobion\OpenApiSymfonyRouting\Tests\Annotations\Fixtures\SeveralClasses; use OpenApi\Annotations as OA; diff --git a/tests/Fixtures/SeveralClasses/FooController.php b/tests/Annotations/Fixtures/SeveralClasses/FooController.php similarity index 76% rename from tests/Fixtures/SeveralClasses/FooController.php rename to tests/Annotations/Fixtures/SeveralClasses/FooController.php index 1759110..71b915a 100644 --- a/tests/Fixtures/SeveralClasses/FooController.php +++ b/tests/Annotations/Fixtures/SeveralClasses/FooController.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Tobion\OpenApiSymfonyRouting\Tests\Fixtures\SeveralClasses; +namespace Tobion\OpenApiSymfonyRouting\Tests\Annotations\Fixtures\SeveralClasses; use OpenApi\Annotations as OA; diff --git a/tests/Fixtures/SeveralClasses/SubNamespace/SubController.php b/tests/Annotations/Fixtures/SeveralClasses/SubNamespace/SubController.php similarity index 73% rename from tests/Fixtures/SeveralClasses/SubNamespace/SubController.php rename to tests/Annotations/Fixtures/SeveralClasses/SubNamespace/SubController.php index cea9f38..22d835a 100644 --- a/tests/Fixtures/SeveralClasses/SubNamespace/SubController.php +++ b/tests/Annotations/Fixtures/SeveralClasses/SubNamespace/SubController.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Tobion\OpenApiSymfonyRouting\Tests\Fixtures\SeveralClasses\SubNamespace; +namespace Tobion\OpenApiSymfonyRouting\Tests\Annotations\Fixtures\SeveralClasses\SubNamespace; use OpenApi\Annotations as OA; diff --git a/tests/Fixtures/SeveralHttpMethods/Controller.php b/tests/Annotations/Fixtures/SeveralHttpMethods/Controller.php similarity index 91% rename from tests/Fixtures/SeveralHttpMethods/Controller.php rename to tests/Annotations/Fixtures/SeveralHttpMethods/Controller.php index 16cb272..0f73f35 100644 --- a/tests/Fixtures/SeveralHttpMethods/Controller.php +++ b/tests/Annotations/Fixtures/SeveralHttpMethods/Controller.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Tobion\OpenApiSymfonyRouting\Tests\Fixtures\SeveralHttpMethods; +namespace Tobion\OpenApiSymfonyRouting\Tests\Annotations\Fixtures\SeveralHttpMethods; use OpenApi\Annotations as OA; diff --git a/tests/Fixtures/SeveralRoutesOnOneAction/Controller.php b/tests/Annotations/Fixtures/SeveralRoutesOnOneAction/Controller.php similarity index 87% rename from tests/Fixtures/SeveralRoutesOnOneAction/Controller.php rename to tests/Annotations/Fixtures/SeveralRoutesOnOneAction/Controller.php index 6c34762..6749df2 100644 --- a/tests/Fixtures/SeveralRoutesOnOneAction/Controller.php +++ b/tests/Annotations/Fixtures/SeveralRoutesOnOneAction/Controller.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Tobion\OpenApiSymfonyRouting\Tests\Fixtures\SeveralRoutesOnOneAction; +namespace Tobion\OpenApiSymfonyRouting\Tests\Annotations\Fixtures\SeveralRoutesOnOneAction; use OpenApi\Annotations as OA; diff --git a/tests/OpenApiRouteLoaderTest.php b/tests/Annotations/OpenApiRouteLoaderAnnotationsTest.php similarity index 88% rename from tests/OpenApiRouteLoaderTest.php rename to tests/Annotations/OpenApiRouteLoaderAnnotationsTest.php index 2003010..59e2ac1 100644 --- a/tests/OpenApiRouteLoaderTest.php +++ b/tests/Annotations/OpenApiRouteLoaderAnnotationsTest.php @@ -2,27 +2,27 @@ declare(strict_types=1); -namespace Tobion\OpenApiSymfonyRouting\Tests; +namespace Tobion\OpenApiSymfonyRouting\Tests\Annotations; use PHPUnit\Framework\TestCase; use Symfony\Component\Finder\Exception\DirectoryNotFoundException; use Symfony\Component\Routing\Route; use Symfony\Component\Routing\RouteCollection; use Tobion\OpenApiSymfonyRouting\OpenApiRouteLoader; -use Tobion\OpenApiSymfonyRouting\Tests\Fixtures\Basic\Controller as BasicController; -use Tobion\OpenApiSymfonyRouting\Tests\Fixtures\FormatSuffix\Controller as FormatSuffixController; -use Tobion\OpenApiSymfonyRouting\Tests\Fixtures\OperationId\Controller as OperationIdController; -use Tobion\OpenApiSymfonyRouting\Tests\Fixtures\PathParameterPattern\Controller as PathParameterPatternController; -use Tobion\OpenApiSymfonyRouting\Tests\Fixtures\Priority\Controller as PriorityController; -use Tobion\OpenApiSymfonyRouting\Tests\Fixtures\SeveralClasses\BarController; -use Tobion\OpenApiSymfonyRouting\Tests\Fixtures\SeveralClasses\FooController; -use Tobion\OpenApiSymfonyRouting\Tests\Fixtures\SeveralClasses\SubNamespace\SubController; -use Tobion\OpenApiSymfonyRouting\Tests\Fixtures\SeveralHttpMethods\Controller as SeveralHttpMethodsController; -use Tobion\OpenApiSymfonyRouting\Tests\Fixtures\SeveralRoutesOnOneAction\Controller as SeveralRoutesOnOneActionController; - -class OpenApiRouteLoaderTest extends TestCase +use Tobion\OpenApiSymfonyRouting\Tests\Annotations\Fixtures\Basic\Controller as BasicController; +use Tobion\OpenApiSymfonyRouting\Tests\Annotations\Fixtures\FormatSuffix\Controller as FormatSuffixController; +use Tobion\OpenApiSymfonyRouting\Tests\Annotations\Fixtures\OperationId\Controller as OperationIdController; +use Tobion\OpenApiSymfonyRouting\Tests\Annotations\Fixtures\PathParameterPattern\Controller as PathParameterPatternController; +use Tobion\OpenApiSymfonyRouting\Tests\Annotations\Fixtures\Priority\Controller as PriorityController; +use Tobion\OpenApiSymfonyRouting\Tests\Annotations\Fixtures\SeveralClasses\BarController; +use Tobion\OpenApiSymfonyRouting\Tests\Annotations\Fixtures\SeveralClasses\FooController; +use Tobion\OpenApiSymfonyRouting\Tests\Annotations\Fixtures\SeveralClasses\SubNamespace\SubController; +use Tobion\OpenApiSymfonyRouting\Tests\Annotations\Fixtures\SeveralHttpMethods\Controller as SeveralHttpMethodsController; +use Tobion\OpenApiSymfonyRouting\Tests\Annotations\Fixtures\SeveralRoutesOnOneAction\Controller as SeveralRoutesOnOneActionController; + +final class OpenApiRouteLoaderAnnotationsTest extends TestCase { - private const FIXTURES_ROUTE_NAME_PREFIX = 'tobion_openapisymfonyrouting_tests_fixtures_'; + private const FIXTURES_ROUTE_NAME_PREFIX = 'tobion_openapisymfonyrouting_tests_annotations_fixtures_'; public function testBasic(): void { diff --git a/tests/Attributes/Fixtures/Basic/Controller.php b/tests/Attributes/Fixtures/Basic/Controller.php new file mode 100644 index 0000000..c72d368 --- /dev/null +++ b/tests/Attributes/Fixtures/Basic/Controller.php @@ -0,0 +1,17 @@ + [ + "enabled" => true + ] + ] +)] +class Controller +{ + #[OA\Get(path: "/a",)] + #[OA\Response(response: "200", description: "Success")] + public function inheritEnabledFormatSuffix(): void + { + } + + #[OA\Get(path: "/b", x: ["format-suffix" => ["pattern" => "json|xml"]])] + #[OA\Response(response: "200", description: "Success")] + public function defineFormatPattern(): void + { + } + + #[OA\Get(path: "/c", x: ["format-suffix" => false])] + #[OA\Response(response: "200", description: "Success")] + public function disableFormatSuffix(): void + { + } +} diff --git a/tests/Attributes/Fixtures/OperationId/Controller.php b/tests/Attributes/Fixtures/OperationId/Controller.php new file mode 100644 index 0000000..8fc22ea --- /dev/null +++ b/tests/Attributes/Fixtures/OperationId/Controller.php @@ -0,0 +1,17 @@ + -100])] + #[OA\Response(response:"200", description:"Success")] + public function catchall(): void + { + } + + #[OA\Get(path:"/bar", x: ["priority" => 10])] + #[OA\Response(response:"200", description:"Success")] + public function bar(): void + { + } +} diff --git a/tests/Attributes/Fixtures/SeveralClasses/BarController.php b/tests/Attributes/Fixtures/SeveralClasses/BarController.php new file mode 100644 index 0000000..b4f81bd --- /dev/null +++ b/tests/Attributes/Fixtures/SeveralClasses/BarController.php @@ -0,0 +1,17 @@ += 8.1 + */ +final class OpenApiRouteLoaderAttributesTest extends TestCase +{ + private const FIXTURES_ROUTE_NAME_PREFIX = 'tobion_openapisymfonyrouting_tests_attributes_fixtures_'; + + public function testBasic(): void + { + $routeLoader = OpenApiRouteLoader::fromDirectories(__DIR__.'/Fixtures/Basic'); + + $routes = $routeLoader->__invoke(); + + $expectedRoutes = new RouteCollection(); + $expectedRoutes->add( + self::FIXTURES_ROUTE_NAME_PREFIX.'basic__invoke', + (new Route('/foobar'))->setMethods('GET')->setDefault('_controller', BasicController::class.'::__invoke') + ); + + self::assertEquals($expectedRoutes, $routes); + } + + public function testFormatSuffix(): void + { + $routeLoader = OpenApiRouteLoader::fromDirectories(__DIR__.'/Fixtures/FormatSuffix'); + + $routes = $routeLoader->__invoke(); + + $expectedRoutes = new RouteCollection(); + $expectedRoutes->add( + self::FIXTURES_ROUTE_NAME_PREFIX.'formatsuffix_inheritenabledformatsuffix', + (new Route('/a.{_format}'))->setDefault('_format', null)->setMethods('GET')->setDefault('_controller', FormatSuffixController::class.'::inheritEnabledFormatSuffix') + ); + $expectedRoutes->add( + self::FIXTURES_ROUTE_NAME_PREFIX.'formatsuffix_defineformatpattern', + (new Route('/b.{_format}'))->setDefault('_format', null)->setRequirement('_format', 'json|xml')->setMethods('GET')->setDefault('_controller', FormatSuffixController::class.'::defineFormatPattern') + ); + $expectedRoutes->add( + self::FIXTURES_ROUTE_NAME_PREFIX.'formatsuffix_disableformatsuffix', + (new Route('/c'))->setMethods('GET')->setDefault('_controller', FormatSuffixController::class.'::disableFormatSuffix') + ); + + self::assertEquals($expectedRoutes, $routes); + } + + public function testOperationId(): void + { + $routeLoader = OpenApiRouteLoader::fromDirectories(__DIR__.'/Fixtures/OperationId'); + + $routes = $routeLoader->__invoke(); + + $expectedRoutes = new RouteCollection(); + $expectedRoutes->add( + 'my-name', + (new Route('/foobar'))->setMethods('GET')->setDefault('_controller', OperationIdController::class.'::__invoke') + ); + + self::assertEquals($expectedRoutes, $routes); + } + + public function testPathParameterPattern(): void + { + $routeLoader = OpenApiRouteLoader::fromDirectories(__DIR__.'/Fixtures/PathParameterPattern'); + + $routes = $routeLoader->__invoke(); + + $expectedRoutes = new RouteCollection(); + $expectedRoutes->add( + self::FIXTURES_ROUTE_NAME_PREFIX.'pathparameterpattern_nopattern', + (new Route('/foo/{id}'))->setMethods('GET')->setDefault('_controller', PathParameterPatternController::class.'::noPattern') + ); + $expectedRoutes->add( + self::FIXTURES_ROUTE_NAME_PREFIX.'pathparameterpattern_noschema', + (new Route('/baz/{id}'))->setMethods('GET')->setDefault('_controller', PathParameterPatternController::class.'::noSchema') + ); + + // OpenAPI needs the param pattern to be anchored (^$) to have the desired effect. Symfony automatically trims those to get a valid full path regex. + $expectedRoutes->add( + self::FIXTURES_ROUTE_NAME_PREFIX.'pathparameterpattern_withpattern', + (new Route('/bar/{id}/{type}')) + ->setRequirement('id', '^[a-zA-Z0-9]+$') + ->setRequirement('type', 'internal|external') + ->setMethods('GET')->setDefault('_controller', PathParameterPatternController::class.'::withPattern') + ); + + self::assertEquals($expectedRoutes, $routes); + } + + public function testPriority(): void + { + $routeLoader = OpenApiRouteLoader::fromDirectories(__DIR__.'/Fixtures/Priority'); + + $routes = $routeLoader->__invoke(); + + $expectedRoutes = new RouteCollection(); + $expectedRoutes->add( + self::FIXTURES_ROUTE_NAME_PREFIX.'priority_foo', + (new Route('/foo'))->setMethods('GET')->setDefault('_controller', PriorityController::class.'::foo') + ); + $expectedRoutes->add( + self::FIXTURES_ROUTE_NAME_PREFIX.'priority_catchall', + (new Route('/{catchall}'))->setMethods('GET')->setDefault('_controller', PriorityController::class.'::catchall'), + -100 + ); + $expectedRoutes->add( + self::FIXTURES_ROUTE_NAME_PREFIX.'priority_bar', + (new Route('/bar'))->setMethods('GET')->setDefault('_controller', PriorityController::class.'::bar'), + 10 + ); + + self::assertEquals($expectedRoutes, $routes); + } + + public function testSeveralClasses(): void + { + $routeLoader = OpenApiRouteLoader::fromDirectories(__DIR__.'/Fixtures/SeveralClasses'); + + $routes = $routeLoader->__invoke(); + + $expectedRoutes = new RouteCollection(); + $expectedRoutes->add( + self::FIXTURES_ROUTE_NAME_PREFIX.'severalclasses_bar__invoke', + (new Route('/bar'))->setMethods('GET')->setDefault('_controller', BarController::class.'::__invoke') + ); + $expectedRoutes->add( + self::FIXTURES_ROUTE_NAME_PREFIX.'severalclasses_foo__invoke', + (new Route('/foo'))->setMethods('GET')->setDefault('_controller', FooController::class.'::__invoke') + ); + $expectedRoutes->add( + self::FIXTURES_ROUTE_NAME_PREFIX.'severalclasses_subnamespace_sub__invoke', + (new Route('/sub'))->setMethods('GET')->setDefault('_controller', SubController::class.'::__invoke') + ); + + self::assertEquals($expectedRoutes, $routes); + } + + public function testSeveralHttpMethods(): void + { + $routeLoader = OpenApiRouteLoader::fromDirectories(__DIR__.'/Fixtures/SeveralHttpMethods'); + + $routes = $routeLoader->__invoke(); + + $expectedRoutes = new RouteCollection(); + $expectedRoutes->add( + self::FIXTURES_ROUTE_NAME_PREFIX.'severalhttpmethods_get', + (new Route('/foobar'))->setMethods('GET')->setDefault('_controller', SeveralHttpMethodsController::class.'::get') + ); + $expectedRoutes->add( + self::FIXTURES_ROUTE_NAME_PREFIX.'severalhttpmethods_put', + (new Route('/foobar'))->setMethods('PUT')->setDefault('_controller', SeveralHttpMethodsController::class.'::put') + ); + $expectedRoutes->add( + self::FIXTURES_ROUTE_NAME_PREFIX.'severalhttpmethods_post', + (new Route('/foobar'))->setMethods('POST')->setDefault('_controller', SeveralHttpMethodsController::class.'::post') + ); + $expectedRoutes->add( + self::FIXTURES_ROUTE_NAME_PREFIX.'severalhttpmethods_delete', + (new Route('/foobar'))->setMethods('DELETE')->setDefault('_controller', SeveralHttpMethodsController::class.'::delete') + ); + + self::assertEquals($expectedRoutes, $routes); + } + + public function testSeveralRoutesOnOneAction(): void + { + $routeLoader = OpenApiRouteLoader::fromDirectories(__DIR__.'/Fixtures/SeveralRoutesOnOneAction'); + + $routes = $routeLoader->__invoke(); + + $expectedRoutes = new RouteCollection(); + $expectedRoutes->add( + self::FIXTURES_ROUTE_NAME_PREFIX.'severalroutesononeaction__invoke', + (new Route('/foobar'))->setMethods('GET')->setDefault('_controller', SeveralRoutesOnOneActionController::class.'::__invoke') + ); + $expectedRoutes->add( + self::FIXTURES_ROUTE_NAME_PREFIX.'severalroutesononeaction__invoke_1', + (new Route('/foobar'))->setMethods('POST')->setDefault('_controller', SeveralRoutesOnOneActionController::class.'::__invoke') + ); + $expectedRoutes->add( + 'my-name', + (new Route('/foo-bar'))->setMethods('GET')->setDefault('_controller', SeveralRoutesOnOneActionController::class.'::__invoke') + ); + + self::assertEquals($expectedRoutes, $routes); + } + + public function testSeveralDirectories(): void + { + $routeLoader = OpenApiRouteLoader::fromDirectories(__DIR__.'/Fixtures/Basic', __DIR__.'/Fixtures/SeveralClasses/SubNamespace'); + + $routes = $routeLoader->__invoke(); + + $expectedRoutes = new RouteCollection(); + $expectedRoutes->add( + self::FIXTURES_ROUTE_NAME_PREFIX.'basic__invoke', + (new Route('/foobar'))->setMethods('GET')->setDefault('_controller', BasicController::class.'::__invoke') + ); + $expectedRoutes->add( + self::FIXTURES_ROUTE_NAME_PREFIX.'severalclasses_subnamespace_sub__invoke', + (new Route('/sub'))->setMethods('GET')->setDefault('_controller', SubController::class.'::__invoke') + ); + + self::assertEquals($expectedRoutes, $routes); + } + + public function testSrcDirectoryDoesNotExist(): void + { + self::expectException(DirectoryNotFoundException::class); + self::expectExceptionMessage('/../../../../src" directory does not exist'); + + OpenApiRouteLoader::fromSrcDirectory(); + } +}