Skip to content

Commit

Permalink
Fix OpenAPI documentation (#75)
Browse files Browse the repository at this point in the history
  • Loading branch information
Luehrsen authored Jan 8, 2024
1 parent 7ffea8b commit 49d2de9
Show file tree
Hide file tree
Showing 6 changed files with 195 additions and 88 deletions.
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"jms/serializer": "^3.28",
"jms/serializer-bundle": "^5.3",
"knplabs/doctrine-behaviors": "^2.6",
"nelmio/api-doc-bundle": "^4.12",
"nelmio/api-doc-bundle": "^4.16",
"nelmio/cors-bundle": "^2.3",
"phpdocumentor/reflection-docblock": "^5.3",
"phpstan/phpdoc-parser": "^1.24",
Expand Down
70 changes: 35 additions & 35 deletions composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions config/services.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,6 @@ services:

# add more service definitions when explicit configuration is needed
# please note that last definitions always *replace* previous ones

App\OpenApi\SchemaQueryParameter:
tags: ['nelmio_api_doc.swagger.processor']
60 changes: 10 additions & 50 deletions src/Controller/Api/ThemesController.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,56 +40,16 @@ public function __construct(
*/
#[REST\Get('/', name: 'api_themes_get')]
#[REST\View(serializerGroups: ['read'])]
#[OA\Parameter(
in: 'query',
name: 'search',
description: 'Search for themes.',
example: 'Twenty',
schema: new OA\Schema(
type: 'string',
minLength: 3,
maxLength: 128,
),
)]
#[OA\Parameter(
in: 'query',
name: 'page',
description: 'The page number.',
example: 1,
schema: new OA\Schema(
type: 'integer',
minimum: 1,
),
)]
#[OA\Parameter(
in: 'query',
name: 'per_page',
description: 'The number of items per page.',
example: 10,
schema: new OA\Schema(
type: 'integer',
minimum: 1,
maximum: 100,
),
)]
#[OA\Parameter(
in: 'query',
name: 'order',
description: 'The order of the items.',
example: 'ASC',
schema: new OA\Schema(
type: 'string',
enum: ['ASC', 'DESC'],
),
)]
#[OA\Parameter(
in: 'query',
name: 'order_by',
description: 'The field to order the items by.',
example: 'name',
schema: new OA\Schema(
type: 'string',
)
#[OA\Get(
x: [
'query-args-explode' => new OA\Schema(
type: 'string',
ref: new Model(
type: ThemeFilter::class,
groups: ['read'],
),
),
]
)]
#[OA\Response(
response: 200,
Expand Down
6 changes: 4 additions & 2 deletions src/ControllerFilter/Traits/OrderFilterTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@ trait OrderFilterTrait
* @var string
*/
#[Serializer\Type('string')]
#[Serializer\Groups(["read"])]
#[OA\Property(type: 'string', description: 'The field to order by.', example: 'id')]
private string $orderBy = 'id';
private $orderBy = 'id';

/**
* The direction to order by.
Expand All @@ -27,9 +28,10 @@ trait OrderFilterTrait
* @var string
*/
#[Serializer\Type('string')]
#[Serializer\Groups(["read"])]
#[OA\Property(type: 'string', description: 'The direction to order by.', example: 'ASC')]
#[Assert\Choice(choices: ['ASC', 'DESC'])]
private string $order = 'ASC';
private $order = 'ASC';

/**
* Get the field to order by.
Expand Down
142 changes: 142 additions & 0 deletions src/OpenApi/SchemaQueryParameter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
<?php

namespace App\OpenApi;

use OpenApi\Analysis;
use OpenApi\Annotations\Operation;
use OpenApi\Annotations as OA;
use OpenApi\Generator;
use Nelmio\ApiDocBundle\OpenApiPhp\Util;

/**
* Custom processor to translate the vendor tag `` into query parameter annotations.
*
* Details for the parameters are taken from the referenced schema.
*/
class SchemaQueryParameter
{
const X_QUERY_AGS_REF = 'query-args-explode';

/**
* @param Analysis $analysis
*/
protected $analysis;

public function __invoke(Analysis $analysis)
{
$this->analysis = $analysis;

/** @var OA\Parameter[] $schemas */
$parameters = $analysis->getAnnotationsOfType(OA\Parameter::class);

/** @var OA\Operation[] $operations */
$operations = $analysis->getAnnotationsOfType(OA\Operation::class);


foreach ($operations as $operation) {
if ($operation->x !== GENERATOR::UNDEFINED && array_key_exists(self::X_QUERY_AGS_REF, $operation->x)) {
// Check if the ref exists and is a string.
if (is_string($operation->x[self::X_QUERY_AGS_REF]->ref)) {
$schema = $this->schemaForRef($operation->x[self::X_QUERY_AGS_REF]->ref);

// Check if the schema is of type 'OA\Schema' and if it has properties.
if ($schema && $schema instanceof OA\Schema && $schema->properties !== GENERATOR::UNDEFINED) {
$this->expandQueryArgs($operation, $schema);
$this->cleanUp($operation, $schema);
}
}
}
}
}

/**
* Find schema for the given ref.
*
* @param Schema[] $schemas
* @param string $ref
*/
protected function schemaForRef(string $ref)
{
$name = str_replace(OA\Components::SCHEMA_REF, '', $ref);
$schema = Util::getSchema($this->analysis->openapi, $name);

if ($schema) {
return $schema;
}

return null;
}

/**
* Expand the given operation by injecting parameters for all properties of the given schema.
*/
protected function expandQueryArgs(Operation $operation, OA\Schema $schema)
{

$operation->parameters = $operation->parameters === GENERATOR::UNDEFINED ? [] : $operation->parameters;

// Extract the properties from the schema.
$properties = $schema->properties;

// Loop through the properties and create a parameter for each.
foreach ($properties as $property) {
if (!($property instanceof OA\Property)) {
continue;
}

$parameterName = $property->property;

// If the property is an array, we need to add the [] to the name.
if ($property->type === 'array') {
$parameterName .= '[]';
}

$parameter = new OA\Parameter([
'name' => $parameterName,
'in' => 'query',
'required' => $property->required,
'description' => $property->description,
'schema' => $property,
]);

$operation->parameters[] = $parameter;
}
}

/**
* Clean up.
*/
protected function cleanUp(OA\Operation $operation, OA\Schema $schema)
{

/** @var OA\OpenApi */
$api = $this->analysis->openapi;

// Find the key for the schema.
$key = null;
foreach ($api->components->schemas as $k => $v) {
if ($v === $schema) {
$key = $k;
break;
}
}

// Remove the schema from the components.
if ($key !== null) {
unset($api->components->schemas[$key]);
}

unset($operation->x[self::X_QUERY_AGS_REF]);
if (!$operation->x) {
$operation->x = GENERATOR::UNDEFINED;
}
}

/**
* Helper function to check if a given values is "undefined" in the context of the OpenApiPhp library.
*/
protected function isUndefined($value)
{
return $value === GENERATOR::UNDEFINED;
}
}

0 comments on commit 49d2de9

Please sign in to comment.