-
-
Notifications
You must be signed in to change notification settings - Fork 833
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Symfony attribute describers (#2112)
* Add SymfonyDescriber * Add SymfonyDescriber dependency injection * Fix codestyle * fixup! Fix codestyle * Add php 8 checks * Add symfony.xml loading * Temp: increase max self deprecations * Add Exception throw when invalid php version is used * Fix codestyle * Add describeRequestBody method * Use elseif * Only check for php version once * Add SymfonyDescriberTest for MapRequestPayload * Fix annotation * Skip test if attributes don't exist * Skip test based on php version * Move $mapRequestPayload type to annotation * Fix annotation style * Fix SymfonyDescriberTest for older symfony versions * Remove version check * Remove usage of in_array to check for attribute * Change elseif to separate if statement * Fix testMapRequestPayloadParamRegistersRequestBody for split up if statement * Add testMapQueryParameter * Fix codestyle * Remove newline * Expand docs for symfony controller mapping * Add backticks * Clarify docs * Upgrade major_version to 6 * Upgrade versionadded_directive_min_version to 6.0 * Revert max allowed self deprecations * Revert phpunit.xml.dist changes * Revert "Revert max allowed self deprecations" This reverts commit c75b539. * Remove not working generator bypass and replace with iterable * Update testMapQueryParameter to work with 'controller' classes * Update testMapRequestPayload to work with 'controller' classes * Remove check for MapQueryString existence * Fix codestyle * Swap comparison order * initial MapQueryString setup * Move annotation describe methods to their own SymfonyAnnotationDescriber classes * Cleanup * Add annotation describer services * Move to own test files * Cleanup * Call setModelRegistry on annotation describers * Use accessible values for tests * Add SymfonyMapQueryStringDescriberTest * Only check availability of needed attribute * Fix message * Fix styleci * Fix styleci * Expand SymfonyMapQueryStringDescriber to copy property data to query parameter data * Fix style * Add missing newline * Fix test php 7.2 compatability * Remove annotation var name * Fix missing values * Fix missing values * Add DTO testing class * Expand SymfonyMapQueryStringDescriberTest * Add SymfonyDescriberTest tests * Remove unused import * Remove trailing commas * Copy ref * Remove setting allowEmptyValue * Remove empty value test * Update documentation * Merge documentation instead of overwriting * Expand symfony controller mapping attribute documentation * Fix RST * Fix RST (missing blank line) * Revert max self deprecations * Use modelDescriber to describe model instead of registering all models * Create weak context * Get schema from property instead of manually setting every property * Add newline at end of file * Fix style * Prevent overwriting non-default values * Add functional test for MapQueryString * Use modifyAnnotationValue helper method instead of overwriting * Fix incorrect name is used for query * Transform int to integer * Remove allowEmptyValue * Fix type comparison * Fix enum not being used in test * Add MapQueryParameter functional tests * Update required statement * Set requestBody required * Add MapRequestPayload functional tests * Fix style * Cleanup array format check * add required field to test * fix baseline * refactor logic to use symfony metadata instead of reflection * style fix * re-add manually iterating over describers * re-add unit tests * style fix * remove named parameter * move xml load logic to describers * Revert "move xml load logic to describers" This reverts commit 035db3f. * major refactor * remove tests * style fix * expand symfony map attribute tests * fix multiple models generated when null * generate proper nullable * style fix * remove property property from schema * style fix * handle reflection exception * rename dir * style fix * Move MapRequestPayload describing to swagger processor * fix baseline * test overwriting to different model * query testing for schema overwriting * documentation update
- Loading branch information
1 parent
e1405ef
commit 9b642df
Showing
16 changed files
with
1,462 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Nelmio\ApiDocBundle\Processors; | ||
|
||
use Nelmio\ApiDocBundle\OpenApiPhp\Util; | ||
use Nelmio\ApiDocBundle\RouteDescriber\RouteArgumentDescriber\SymfonyMapQueryStringDescriber; | ||
use OpenApi\Analysis; | ||
use OpenApi\Annotations as OA; | ||
use OpenApi\Generator; | ||
use OpenApi\Processors\ProcessorInterface; | ||
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; | ||
|
||
/** | ||
* A processor that adds query parameters to operations that have a MapQueryString attribute. | ||
* A processor is used to ensure that a Model has been created. | ||
* | ||
* @see SymfonyMapQueryStringDescriber | ||
*/ | ||
final class MapQueryStringProcessor implements ProcessorInterface | ||
{ | ||
public function __invoke(Analysis $analysis) | ||
{ | ||
/** @var OA\Operation[] $operations */ | ||
$operations = $analysis->getAnnotationsOfType(OA\Operation::class); | ||
|
||
foreach ($operations as $operation) { | ||
if (!isset($operation->_context->{SymfonyMapQueryStringDescriber::CONTEXT_ARGUMENT_METADATA})) { | ||
continue; | ||
} | ||
|
||
$argumentMetaData = $operation->_context->{SymfonyMapQueryStringDescriber::CONTEXT_ARGUMENT_METADATA}; | ||
if (!$argumentMetaData instanceof ArgumentMetadata) { | ||
throw new \LogicException(sprintf('MapQueryString ArgumentMetaData not found for operation "%s"', $operation->operationId)); | ||
} | ||
|
||
$modelRef = $operation->_context->{SymfonyMapQueryStringDescriber::CONTEXT_MODEL_REF}; | ||
if (!isset($modelRef)) { | ||
throw new \LogicException(sprintf('MapQueryString Model reference not found for operation "%s"', $operation->operationId)); | ||
} | ||
|
||
$nativeModelName = str_replace(OA\Components::SCHEMA_REF, '', $modelRef); | ||
|
||
$schemaModel = Util::getSchema($analysis->openapi, $nativeModelName); | ||
|
||
// There are no properties to map to query parameters | ||
if (Generator::UNDEFINED === $schemaModel->properties) { | ||
return; | ||
} | ||
|
||
$isModelOptional = $argumentMetaData->hasDefaultValue() || $argumentMetaData->isNullable(); | ||
|
||
foreach ($schemaModel->properties as $property) { | ||
$operationParameter = Util::getOperationParameter($operation, $property->property, 'query'); | ||
|
||
// Remove incompatible properties | ||
$propertyVars = get_object_vars($property); | ||
unset($propertyVars['property']); | ||
|
||
$schema = new OA\Schema($propertyVars); | ||
|
||
Util::modifyAnnotationValue($operationParameter, 'schema', $schema); | ||
Util::modifyAnnotationValue($operationParameter, 'name', $property->property); | ||
Util::modifyAnnotationValue($operationParameter, 'description', $schema->description); | ||
Util::modifyAnnotationValue($operationParameter, 'required', $schema->required); | ||
Util::modifyAnnotationValue($operationParameter, 'deprecated', $schema->deprecated); | ||
Util::modifyAnnotationValue($operationParameter, 'example', $schema->example); | ||
|
||
if ($isModelOptional) { | ||
Util::modifyAnnotationValue($operationParameter, 'required', false); | ||
} elseif (is_array($schemaModel->required) && in_array($property->property, $schemaModel->required, true)) { | ||
Util::modifyAnnotationValue($operationParameter, 'required', true); | ||
} else { | ||
Util::modifyAnnotationValue($operationParameter, 'required', false); | ||
} | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Nelmio\ApiDocBundle\Processors; | ||
|
||
use Nelmio\ApiDocBundle\OpenApiPhp\Util; | ||
use Nelmio\ApiDocBundle\RouteDescriber\RouteArgumentDescriber\SymfonyMapRequestPayloadDescriber; | ||
use OpenApi\Analysis; | ||
use OpenApi\Annotations as OA; | ||
use OpenApi\Processors\ProcessorInterface; | ||
use Symfony\Component\HttpKernel\Attribute\MapRequestPayload; | ||
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; | ||
|
||
/** | ||
* A processor that adds query parameters to operations that have a MapRequestPayload attribute. | ||
* A processor is used to ensure that a Model has been created. | ||
* | ||
* @see SymfonyMapRequestPayloadDescriber | ||
*/ | ||
final class MapRequestPayloadProcessor implements ProcessorInterface | ||
{ | ||
public function __invoke(Analysis $analysis) | ||
{ | ||
/** @var OA\Operation[] $operations */ | ||
$operations = $analysis->getAnnotationsOfType(OA\Operation::class); | ||
|
||
foreach ($operations as $operation) { | ||
if (!isset($operation->_context->{SymfonyMapRequestPayloadDescriber::CONTEXT_ARGUMENT_METADATA})) { | ||
continue; | ||
} | ||
|
||
$argumentMetaData = $operation->_context->{SymfonyMapRequestPayloadDescriber::CONTEXT_ARGUMENT_METADATA}; | ||
if (!$argumentMetaData instanceof ArgumentMetadata) { | ||
throw new \LogicException(sprintf('MapRequestPayload ArgumentMetaData not found for operation "%s"', $operation->operationId)); | ||
} | ||
|
||
/** @var MapRequestPayload $attribute */ | ||
if (!$attribute = $argumentMetaData->getAttributes(MapRequestPayload::class, ArgumentMetadata::IS_INSTANCEOF)[0] ?? null) { | ||
throw new \LogicException(sprintf('Operation "%s" does not contain attribute of "%s', $operation->operationId, MapRequestPayload::class)); | ||
} | ||
|
||
$modelRef = $operation->_context->{SymfonyMapRequestPayloadDescriber::CONTEXT_MODEL_REF}; | ||
if (!isset($modelRef)) { | ||
throw new \LogicException(sprintf('MapRequestPayload Model reference not found for operation "%s"', $operation->operationId)); | ||
} | ||
|
||
/** @var OA\RequestBody $requestBody */ | ||
$requestBody = Util::getChild($operation, OA\RequestBody::class); | ||
Util::modifyAnnotationValue($requestBody, 'required', !($argumentMetaData->hasDefaultValue() || $argumentMetaData->isNullable())); | ||
|
||
$formats = $attribute->acceptFormat; | ||
if (!is_array($formats)) { | ||
$formats = [$attribute->acceptFormat ?? 'json']; | ||
} | ||
|
||
foreach ($formats as $format) { | ||
$contentSchema = $this->getContentSchemaForType($requestBody, $format); | ||
Util::modifyAnnotationValue($contentSchema, 'ref', $modelRef); | ||
|
||
if ($argumentMetaData->isNullable()) { | ||
$contentSchema->nullable = true; | ||
} | ||
} | ||
} | ||
} | ||
|
||
private function getContentSchemaForType(OA\RequestBody $requestBody, string $type): OA\Schema | ||
{ | ||
Util::modifyAnnotationValue($requestBody, 'content', []); | ||
switch ($type) { | ||
case 'json': | ||
$contentType = 'application/json'; | ||
|
||
break; | ||
case 'xml': | ||
$contentType = 'application/xml'; | ||
|
||
break; | ||
default: | ||
throw new \InvalidArgumentException('Unsupported media type'); | ||
} | ||
|
||
if (!isset($requestBody->content[$contentType])) { | ||
$weakContext = Util::createWeakContext($requestBody->_context); | ||
$requestBody->content[$contentType] = new OA\MediaType( | ||
[ | ||
'mediaType' => $contentType, | ||
'_context' => $weakContext, | ||
] | ||
); | ||
} | ||
|
||
return Util::getChild( | ||
$requestBody->content[$contentType], | ||
OA\Schema::class | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.