Skip to content

Commit

Permalink
Merge pull request #63 from niels-nijens/toggle-operation-id
Browse files Browse the repository at this point in the history
Add using the operationId as route name as optional feature to the RouteLoader
  • Loading branch information
niels-nijens authored Jul 3, 2022
2 parents ce682bd + f04c135 commit 8d0d4a7
Show file tree
Hide file tree
Showing 8 changed files with 90 additions and 12 deletions.
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,20 @@ paths:

The value of the `x-symfony-controller` property is the same as you would normally add to a [Symfony route](https://symfony.com/doc/current/routing.html#creating-routes).

#### Using the operationId of an operation as the name of the Symfony route
Within an OpenAPI document, you can give each operation an
[operationId](https://spec.openapis.org/oas/latest.html#fixed-fields-7) to better identify it.
To use the `operationId` as the name for a loaded Symfony route, add the following bundle configuration:

```yaml
# config/packages/nijens_openapi.yaml
nijens_openapi:
routing:
operation_id_as_route_name: true
```

Using the `operationId` for your routes gives you more control over the API route names and allows you to better use them with a `UrlGenerator`.

### Validation of a JSON request body
When the operations of the path items have a `requestBody` property configured with the content-type `application/json`,
the bundle validates the incoming request bodies for those routes in the specification.
Expand Down
16 changes: 16 additions & 0 deletions src/DependencyInjection/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,27 @@ public function getConfigTreeBuilder(): TreeBuilder
$treeBuilder = new TreeBuilder('nijens_openapi');
$rootNode = $treeBuilder->getRootNode();

$this->addRoutingSection($rootNode);
$this->addExceptionsSection($rootNode);

return $treeBuilder;
}

private function addRoutingSection(ArrayNodeDefinition $rootNode): void
{
$rootNode->children()
->arrayNode('routing')
->addDefaultsIfNotSet()
->children()
->booleanNode('operation_id_as_route_name')
->info('Toggle using the path item operation ID from the OpenAPI documents as route name.')
->defaultFalse()
->end()
->end()
->end()
->end();
}

private function addExceptionsSection(ArrayNodeDefinition $rootNode): void
{
$rootNode->children()
Expand Down
8 changes: 8 additions & 0 deletions src/DependencyInjection/NijensOpenapiExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
use Nijens\OpenapiBundle\ExceptionHandling\EventSubscriber\ProblemExceptionToJsonResponseSubscriber;
use Nijens\OpenapiBundle\ExceptionHandling\EventSubscriber\ThrowableToProblemExceptionSubscriber;
use Nijens\OpenapiBundle\ExceptionHandling\ThrowableToProblemExceptionTransformer;
use Nijens\OpenapiBundle\Routing\RouteLoader;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\Config\Loader\LoaderInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
Expand Down Expand Up @@ -44,6 +45,7 @@ public function load(array $configs, ContainerBuilder $container): void
$configuration = new Configuration();
$config = $this->processConfiguration($configuration, $configs);

$this->registerRoutingConfiguration($config['routing'], $container);
$this->registerExceptionHandlingConfiguration($config['exception_handling'], $container);
}

Expand All @@ -59,6 +61,12 @@ private function loadDeprecatedServices(XmlFileLoader $loader): void
$loader->load(sprintf('services_deprecated%s.xml', $deprecatedServicesFileSuffix));
}

private function registerRoutingConfiguration(array $config, ContainerBuilder $container): void
{
$definition = $container->getDefinition(RouteLoader::class);
$definition->replaceArgument(2, $config['operation_id_as_route_name']);
}

private function registerExceptionHandlingConfiguration(array $config, ContainerBuilder $container): void
{
$definition = $container->getDefinition(ThrowableToProblemExceptionTransformer::class);
Expand Down
4 changes: 2 additions & 2 deletions src/Resources/config/services.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@

<parameters>
<parameter key="nijens_openapi.controller.catch_all.class">Nijens\OpenapiBundle\Controller\CatchAllController</parameter>
<parameter key="nijens_openapi.routing.loader.class">Nijens\OpenapiBundle\Routing\RouteLoader</parameter>
<parameter key="nijens_openapi.json.parser.class">Seld\JsonLint\JsonParser</parameter>
<parameter key="nijens_openapi.json.schema_loader.class">Nijens\OpenapiBundle\Json\SchemaLoader</parameter>
<parameter key="nijens_openapi.json.validator.class">JsonSchema\Validator</parameter>
Expand All @@ -20,9 +19,10 @@
<tag name="controller.service_arguments"/>
</service>

<service id="nijens_openapi.routing.loader" class="%nijens_openapi.routing.loader.class%">
<service id="Nijens\OpenapiBundle\Routing\RouteLoader">
<argument type="service" id="file_locator"/>
<argument type="service" id="nijens_openapi.json.schema_loader"/>
<argument>~</argument>

<tag name="routing.loader"/>
</service>
Expand Down
20 changes: 17 additions & 3 deletions src/Routing/RouteLoader.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,23 @@ class RouteLoader extends FileLoader
*/
private $schemaLoader;

/**
* @var bool
*/
private $useOperationIdAsRouteName;

/**
* Constructs a new RouteLoader instance.
*/
public function __construct(FileLocatorInterface $locator, SchemaLoaderInterface $schemaLoader)
{
public function __construct(
FileLocatorInterface $locator,
SchemaLoaderInterface $schemaLoader,
bool $useOperationIdAsRouteName = false
) {
parent::__construct($locator);

$this->schemaLoader = $schemaLoader;
$this->useOperationIdAsRouteName = $useOperationIdAsRouteName;
}

/**
Expand Down Expand Up @@ -136,8 +145,13 @@ private function parseOperation(
$route = new Route($path, $defaults, []);
$route->setMethods($requestMethod);

$routeName = null;
if ($this->useOperationIdAsRouteName && isset($operation->operationId)) {
$routeName = $operation->operationId;
}

$collection->add(
$operation->operationId ?? $this->createRouteName($path, $requestMethod),
$routeName ?? $this->createRouteName($path, $requestMethod),
$route
);
}
Expand Down
2 changes: 2 additions & 0 deletions tests/Functional/App/bundle.test.yaml
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
nijens_openapi:
routing:
operation_id_as_route_name: true
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"paths": {
"/pets/{uuid}": {
"put": {
"operationId": "createPet",
"requestBody": {
"content": {
"application/json": {
Expand Down
37 changes: 30 additions & 7 deletions tests/Routing/RouteLoaderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,8 @@ class RouteLoaderTest extends TestCase
*/
protected function setUp(): void
{
$fileLocator = new FileLocator([
__DIR__.'/../Resources/specifications',
]);
$loader = new ChainLoader([new JsonLoader(), new YamlLoader()]);
$dereferencer = new Dereferencer(new JsonPointer(), $loader);

$schemaLoader = new SchemaLoader($loader, $dereferencer);
$fileLocator = $this->createFileLocator();
$schemaLoader = $this->createSchemaLoader();

$this->routeLoader = new RouteLoader($fileLocator, $schemaLoader);
}
Expand Down Expand Up @@ -156,4 +151,32 @@ public function testLoadWithSymfonyControllerConfigured(): void
$this->assertInstanceOf(Route::class, $route);
$this->assertSame('Nijens\OpenapiBundle\Controller\FooController::bar', $route->getDefault('_controller'));
}

public function testCanUseOperationIdAsRouteName(): void
{
$fileLocator = $this->createFileLocator();
$schemaLoader = $this->createSchemaLoader();

$this->routeLoader = new RouteLoader($fileLocator, $schemaLoader, true);

$routes = $this->routeLoader->load('route-loader-symfony-controller.json', 'openapi');
$route = $routes->get('createPet');

$this->assertInstanceOf(Route::class, $route);
}

private function createFileLocator(): FileLocator
{
return new FileLocator([
__DIR__.'/../Resources/specifications',
]);
}

private function createSchemaLoader(): SchemaLoader
{
$loader = new ChainLoader([new JsonLoader(), new YamlLoader()]);
$dereferencer = new Dereferencer(new JsonPointer(), $loader);

return new SchemaLoader($loader, $dereferencer);
}
}

0 comments on commit 8d0d4a7

Please sign in to comment.