Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(#2297): (Symfony 7.1) Add MapRequestPayload array parameter handling #2298

Merged
Merged
13 changes: 11 additions & 2 deletions src/Processor/MapRequestPayloadProcessor.php
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,17 @@ public function __invoke(Analysis $analysis): void
}

$contentSchema = $this->getContentSchemaForType($requestBody, $format);

Util::modifyAnnotationValue($contentSchema, 'ref', $modelRef);
if ('array' === $argumentMetaData->getType()) {
$contentSchema->type = 'array';
$contentSchema->items = new OA\Items(
[
'ref' => $modelRef,
'_context' => Util::createWeakContext($contentSchema->_context),
]
);
} else {
Util::modifyAnnotationValue($contentSchema, 'ref', $modelRef);
}

if ($argumentMetaData->isNullable()) {
$contentSchema->nullable = true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,15 @@ public function describe(ArgumentMetadata $argumentMetadata, OA\Operation $opera
return;
}

$typeClass = $argumentMetadata->getType();

$reflectionAttribute = new \ReflectionClass(MapRequestPayload::class);
if (Type::BUILTIN_TYPE_ARRAY === $typeClass && $reflectionAttribute->hasProperty('type') && null !== $attribute->type) {
$typeClass = $attribute->type;
}

$modelRef = $this->modelRegistry->register(new Model(
new Type(Type::BUILTIN_TYPE_OBJECT, false, $argumentMetadata->getType()),
new Type(Type::BUILTIN_TYPE_OBJECT, false, $typeClass),
groups: $this->getGroups($attribute),
serializationContext: $attribute->serializationContext,
));
Expand Down
188 changes: 0 additions & 188 deletions tests/Functional/Controller/ApiController81.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,11 @@
use Nelmio\ApiDocBundle\Tests\Functional\Entity\EntityWithObjectType;
use Nelmio\ApiDocBundle\Tests\Functional\Entity\EntityWithRef;
use Nelmio\ApiDocBundle\Tests\Functional\Entity\EntityWithUuid;
use Nelmio\ApiDocBundle\Tests\Functional\Entity\QueryModel\ArrayQueryModel;
use Nelmio\ApiDocBundle\Tests\Functional\Entity\QueryModel\FilterQueryModel;
use Nelmio\ApiDocBundle\Tests\Functional\Entity\QueryModel\PaginationQueryModel;
use Nelmio\ApiDocBundle\Tests\Functional\Entity\QueryModel\SortQueryModel;
use Nelmio\ApiDocBundle\Tests\Functional\Entity\RangeInteger;
use Nelmio\ApiDocBundle\Tests\Functional\Entity\SymfonyConstraints81;
use Nelmio\ApiDocBundle\Tests\Functional\Entity\SymfonyConstraintsWithValidationGroups;
use Nelmio\ApiDocBundle\Tests\Functional\Entity\SymfonyDiscriminator81;
use Nelmio\ApiDocBundle\Tests\Functional\Entity\SymfonyDiscriminatorFileMapping;
use Nelmio\ApiDocBundle\Tests\Functional\Entity\SymfonyMapQueryString;
use Nelmio\ApiDocBundle\Tests\Functional\Entity\User;
use Nelmio\ApiDocBundle\Tests\Functional\EntityExcluded\Symfony7\SerializedNameEntity;
use Nelmio\ApiDocBundle\Tests\Functional\Form\DummyType;
Expand All @@ -48,9 +43,6 @@
use Nelmio\ApiDocBundle\Tests\Functional\Form\FormWithRefType;
use Nelmio\ApiDocBundle\Tests\Functional\Form\UserType;
use OpenApi\Attributes as OA;
use Symfony\Component\HttpKernel\Attribute\MapQueryParameter;
use Symfony\Component\HttpKernel\Attribute\MapQueryString;
use Symfony\Component\HttpKernel\Attribute\MapRequestPayload;
use Symfony\Component\Routing\Annotation\Route;

class ApiController81
Expand Down Expand Up @@ -520,184 +512,4 @@ public function arbitraryArray()
public function dictionary()
{
}

#[Route('/article_map_query_string')]
#[OA\Response(response: '200', description: '')]
public function fetchArticleFromMapQueryString(
#[MapQueryString] SymfonyMapQueryString $article81Query
) {
}

#[Route('/article_map_query_string_nullable')]
#[OA\Response(response: '200', description: '')]
public function fetchArticleFromMapQueryStringNullable(
#[MapQueryString] ?SymfonyMapQueryString $article81Query
) {
}

#[Route('/article_map_query_string_passes_validation_groups')]
#[OA\Response(response: '200', description: '')]
public function fetchArticleFromMapQueryStringHandlesValidationGroups(
#[MapQueryString(validationGroups: ['test'])] SymfonyConstraintsWithValidationGroups $symfonyConstraintsWithValidationGroups,
) {
}

#[Route('/article_map_query_string_overwrite_parameters')]
#[OA\Parameter(
name: 'id',
in: 'query',
schema: new OA\Schema(type: 'string', nullable: true),
description: 'Query parameter id description'
)]
#[OA\Parameter(
name: 'name',
in: 'query',
description: 'Query parameter name description'
)]
#[OA\Parameter(
name: 'nullableName',
in: 'query',
description: 'Query parameter nullableName description'
)]
#[OA\Parameter(
name: 'articleType81',
in: 'query',
description: 'Query parameter articleType81 description'
)]
#[OA\Parameter(
name: 'nullableArticleType81',
in: 'query',
description: 'Query parameter nullableArticleType81 description'
)]
#[OA\Response(response: '200', description: '')]
public function fetchArticleFromMapQueryStringOverwriteParameters(
#[MapQueryString] SymfonyMapQueryString $article81Query
) {
}

#[Route('/article_map_query_string_many_parameters')]
#[OA\Response(response: '200', description: '')]
public function fetchArticleWithManyParameters(
#[MapQueryString] FilterQueryModel $filterQuery,
#[MapQueryString] PaginationQueryModel $paginationQuery,
#[MapQueryString] SortQueryModel $sortQuery,
#[MapQueryString] ArrayQueryModel $arrayQuery,
) {
}

#[Route('/article_map_query_string_many_parameters_optional')]
#[OA\Response(response: '200', description: '')]
public function fetchArticleWithManyOptionalParameters(
#[MapQueryString] ?FilterQueryModel $filterQuery,
#[MapQueryString] ?PaginationQueryModel $paginationQuery,
#[MapQueryString] ?SortQueryModel $sortQuery,
#[MapQueryString] ?ArrayQueryModel $arrayQuery,
) {
}

#[Route('/article_map_query_parameter')]
#[OA\Response(response: '200', description: '')]
public function fetchArticleFromMapQueryParameter(
#[MapQueryParameter] int $someInt,
#[MapQueryParameter] float $someFloat,
#[MapQueryParameter] bool $someBool,
#[MapQueryParameter] string $someString,
#[MapQueryParameter] array $someArray,
) {
}

#[Route('/article_map_query_parameter_validate_filters')]
#[OA\Response(response: '200', description: '')]
public function fetchArticleFromMapQueryParameterValidateFilters(
#[MapQueryParameter(options: ['min_range' => 2, 'max_range' => 1234])] int $minMaxInt,
#[MapQueryParameter(filter: FILTER_VALIDATE_DOMAIN)] string $domain,
#[MapQueryParameter(filter: FILTER_VALIDATE_EMAIL)] string $email,
#[MapQueryParameter(filter: FILTER_VALIDATE_IP)] string $ip,
#[MapQueryParameter(filter: FILTER_VALIDATE_IP, flags: FILTER_FLAG_IPV4)] string $ipv4,
#[MapQueryParameter(filter: FILTER_VALIDATE_IP, flags: FILTER_FLAG_IPV6)] string $ipv6,
#[MapQueryParameter(filter: FILTER_VALIDATE_MAC)] string $macAddress,
#[MapQueryParameter(filter: FILTER_VALIDATE_REGEXP, options: ['regexp' => '/^test/'])] string $regexp,
#[MapQueryParameter(filter: FILTER_VALIDATE_URL)] string $url,
) {
}

#[Route('/article_map_query_parameter_nullable')]
#[OA\Response(response: '200', description: '')]
public function fetchArticleFromMapQueryParameterNullable(
#[MapQueryParameter] ?int $id,
) {
}

#[Route('/article_map_query_parameter_default')]
#[OA\Response(response: '200', description: '')]
public function fetchArticleFromMapQueryParameterDefault(
#[MapQueryParameter] int $id = 123,
) {
}

#[Route('/article_map_query_parameter_overwrite_parameters')]
#[OA\Parameter(
name: 'id',
in: 'query',
description: 'Query parameter id description',
example: 123,
)]
#[OA\Parameter(
name: 'changedType',
in: 'query',
schema: new OA\Schema(type: 'int', nullable: false),
description: 'Incorrectly described query parameter',
example: 123,
)]
#[OA\Response(response: '200', description: '')]
public function fetchArticleFromMapQueryParameterOverwriteParameters(
#[MapQueryParameter] ?int $id,
#[MapQueryParameter] ?string $changedType,
) {
}

#[Route('/article_map_request_payload', methods: ['POST'])]
#[OA\Response(response: '200', description: '')]
public function createArticleFromMapRequestPayload(
#[MapRequestPayload] Article81 $article81,
) {
}

#[Route('/article_map_request_payload_nullable', methods: ['POST'])]
#[OA\Response(response: '200', description: '')]
public function createArticleFromMapRequestPayloadNullable(
#[MapRequestPayload] ?Article81 $article81,
) {
}

#[Route('/article_map_request_payload_overwrite', methods: ['POST'])]
#[OA\RequestBody(
description: 'Request body description',
content: new Model(type: EntityWithNullableSchemaSet::class),
)]
#[OA\Response(response: '200', description: '')]
public function createArticleFromMapRequestPayloadOverwrite(
#[MapRequestPayload] Article81 $article81,
) {
}

#[Route('/article_map_request_payload_handles_already_set_content', methods: ['POST'])]
#[OA\RequestBody(
description: 'Request body description',
content: new OA\JsonContent(
ref: new Model(type: Article81::class)
),
)]
#[OA\Response(response: '200', description: '')]
public function createArticleFromMapRequestPayloadHandlesAlreadySetContent(
#[MapRequestPayload] Article81 $article81,
) {
}

#[Route('/article_map_request_payload_validation_groups', methods: ['POST'])]
#[OA\Response(response: '200', description: '')]
public function createArticleFromMapRequestPayloadPassedValidationGroups(
#[MapRequestPayload(validationGroups: ['test'])] SymfonyConstraintsWithValidationGroups $symfonyConstraintsWithValidationGroups,
) {
}
}
79 changes: 79 additions & 0 deletions tests/Functional/Controller/MapQueryParameterController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<?php

/*
* This file is part of the NelmioApiDocBundle package.
*
* (c) Nelmio
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Nelmio\ApiDocBundle\Tests\Functional\Controller;

use Symfony\Component\HttpKernel\Attribute\MapQueryParameter;
use Symfony\Component\Routing\Annotation\Route;

class MapQueryParameterController
{
#[Route('/article_map_query_parameter', methods: ['GET'])]
#[OA\Response(response: '200', description: '')]
public function fetchArticleFromMapQueryParameter(
#[MapQueryParameter] int $someInt,
#[MapQueryParameter] float $someFloat,
#[MapQueryParameter] bool $someBool,
#[MapQueryParameter] string $someString,
#[MapQueryParameter] array $someArray,
) {
}

#[Route('/article_map_query_parameter_validate_filters', methods: ['GET'])]
#[OA\Response(response: '200', description: '')]
public function fetchArticleFromMapQueryParameterValidateFilters(
#[MapQueryParameter(options: ['min_range' => 2, 'max_range' => 1234])] int $minMaxInt,
#[MapQueryParameter(filter: FILTER_VALIDATE_DOMAIN)] string $domain,
#[MapQueryParameter(filter: FILTER_VALIDATE_EMAIL)] string $email,
#[MapQueryParameter(filter: FILTER_VALIDATE_IP)] string $ip,
#[MapQueryParameter(filter: FILTER_VALIDATE_IP, flags: FILTER_FLAG_IPV4)] string $ipv4,
#[MapQueryParameter(filter: FILTER_VALIDATE_IP, flags: FILTER_FLAG_IPV6)] string $ipv6,
#[MapQueryParameter(filter: FILTER_VALIDATE_MAC)] string $macAddress,
#[MapQueryParameter(filter: FILTER_VALIDATE_REGEXP, options: ['regexp' => '/^test/'])] string $regexp,
#[MapQueryParameter(filter: FILTER_VALIDATE_URL)] string $url,
) {
}

#[Route('/article_map_query_parameter_nullable', methods: ['GET'])]
#[OA\Response(response: '200', description: '')]
public function fetchArticleFromMapQueryParameterNullable(
#[MapQueryParameter] ?int $id,
) {
}

#[Route('/article_map_query_parameter_default', methods: ['GET'])]
#[OA\Response(response: '200', description: '')]
public function fetchArticleFromMapQueryParameterDefault(
#[MapQueryParameter] int $id = 123,
) {
}

#[Route('/article_map_query_parameter_overwrite_parameters', methods: ['GET'])]
#[OA\Parameter(
name: 'id',
in: 'query',
description: 'Query parameter id description',
example: 123,
)]
#[OA\Parameter(
name: 'changedType',
in: 'query',
schema: new OA\Schema(type: 'int', nullable: false),
description: 'Incorrectly described query parameter',
example: 123,
)]
#[OA\Response(response: '200', description: '')]
public function fetchArticleFromMapQueryParameterOverwriteParameters(
#[MapQueryParameter] ?int $id,
#[MapQueryParameter] ?string $changedType,
) {
}
}
10 changes: 5 additions & 5 deletions tests/Functional/Controller/MapQueryStringController.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,28 +24,28 @@

class MapQueryStringController
{
#[Route('/article_map_query_string')]
#[Route('/article_map_query_string', methods: ['GET'])]
#[OA\Response(response: '200', description: '')]
public function fetchArticleFromMapQueryString(
#[MapQueryString] SymfonyMapQueryString $article81Query
) {
}

#[Route('/article_map_query_string_nullable')]
#[Route('/article_map_query_string_nullable', methods: ['GET'])]
#[OA\Response(response: '200', description: '')]
public function fetchArticleFromMapQueryStringNullable(
#[MapQueryString] ?SymfonyMapQueryString $article81Query
) {
}

#[Route('/article_map_query_string_passes_validation_groups')]
#[Route('/article_map_query_string_passes_validation_groups', methods: ['GET'])]
#[OA\Response(response: '200', description: '')]
public function fetchArticleFromMapQueryStringHandlesValidationGroups(
#[MapQueryString(validationGroups: ['test'])] SymfonyConstraintsWithValidationGroups $symfonyConstraintsWithValidationGroups,
) {
}

#[Route('/article_map_query_string_overwrite_parameters')]
#[Route('/article_map_query_string_overwrite_parameters', methods: ['GET'])]
#[OA\Parameter(
name: 'id',
in: 'query',
Expand Down Expand Up @@ -78,7 +78,7 @@ public function fetchArticleFromMapQueryStringOverwriteParameters(
) {
}

#[Route('/article_map_query_string_many_parameters')]
#[Route('/article_map_query_string_many_parameters', methods: ['GET'])]
#[OA\Response(response: '200', description: '')]
public function fetchArticleWithManyParameters(
#[MapQueryString] FilterQueryModel $filterQuery,
Expand Down
Loading
Loading