Skip to content

Commit d2f281e

Browse files
fix(jsonschema): fix recursive documentation when using a dto entity wrapper (#5973)
1 parent dac49cb commit d2f281e

File tree

4 files changed

+91
-3
lines changed

4 files changed

+91
-3
lines changed

features/openapi/docs.feature

+23
Original file line numberDiff line numberDiff line change
@@ -319,3 +319,26 @@ Feature: Documentation support
319319
"nullable":true
320320
}
321321
"""
322+
323+
@!mongodb
324+
Scenario: Retrieve the OpenAPI documentation for Entity Dto Wrappers
325+
Given I send a "GET" request to "/docs.json"
326+
Then the response status code should be 200
327+
And the response should be in JSON
328+
And the header "Content-Type" should be equal to "application/json; charset=utf-8"
329+
And the OpenAPI class "WrappedResponseEntity-read" exists
330+
And the "id" property exists for the OpenAPI class "WrappedResponseEntity-read"
331+
And the "id" property for the OpenAPI class "WrappedResponseEntity-read" should be equal to:
332+
"""
333+
{
334+
"type": "string"
335+
}
336+
"""
337+
And the OpenAPI class "WrappedResponseEntity.CustomOutputEntityWrapperDto-read" exists
338+
And the "data" property exists for the OpenAPI class "WrappedResponseEntity.CustomOutputEntityWrapperDto-read"
339+
And the "data" property for the OpenAPI class "WrappedResponseEntity.CustomOutputEntityWrapperDto-read" should be equal to:
340+
"""
341+
{
342+
"$ref": "#\/components\/schemas\/WrappedResponseEntity-read"
343+
}
344+
"""

src/JsonSchema/SchemaFactory.php

+13-3
Original file line numberDiff line numberDiff line change
@@ -237,8 +237,7 @@ private function buildDefinitionName(string $className, string $format = 'json',
237237
}
238238

239239
if (null !== $inputOrOutputClass && $className !== $inputOrOutputClass) {
240-
$parts = explode('\\', $inputOrOutputClass);
241-
$shortName = end($parts);
240+
$shortName = $this->getShortClassName($inputOrOutputClass);
242241
$prefix .= '.'.$shortName;
243242
}
244243

@@ -274,13 +273,17 @@ private function getMetadata(string $className, string $type = Schema::TYPE_OUTP
274273
];
275274
}
276275

276+
$forceSubschema = $serializerContext[self::FORCE_SUBSCHEMA] ?? false;
277277
if (null === $operation) {
278278
$resourceMetadataCollection = $this->resourceMetadataFactory->create($className);
279279
try {
280280
$operation = $resourceMetadataCollection->getOperation();
281281
} catch (OperationNotFoundException $e) {
282282
$operation = new HttpOperation();
283283
}
284+
if ($operation->getShortName() === $this->getShortClassName($className) && $forceSubschema) {
285+
$operation = new HttpOperation();
286+
}
284287

285288
$operation = $this->findOperationForType($resourceMetadataCollection, $type, $operation);
286289
} else {
@@ -298,7 +301,7 @@ private function getMetadata(string $className, string $type = Schema::TYPE_OUTP
298301

299302
$inputOrOutput = ['class' => $className];
300303
$inputOrOutput = Schema::TYPE_OUTPUT === $type ? ($operation->getOutput() ?? $inputOrOutput) : ($operation->getInput() ?? $inputOrOutput);
301-
$outputClass = ($serializerContext[self::FORCE_SUBSCHEMA] ?? false) ? ($inputOrOutput['class'] ?? $inputOrOutput->class ?? $operation->getClass()) : ($inputOrOutput['class'] ?? $inputOrOutput->class ?? null);
304+
$outputClass = $forceSubschema ? ($inputOrOutput['class'] ?? $inputOrOutput->class ?? $operation->getClass()) : ($inputOrOutput['class'] ?? $inputOrOutput->class ?? null);
302305

303306
if (null === $outputClass) {
304307
// input or output disabled
@@ -374,4 +377,11 @@ private function getFactoryOptions(array $serializerContext, array $validationGr
374377

375378
return $options;
376379
}
380+
381+
private function getShortClassName(string $fullyQualifiedName): string
382+
{
383+
$parts = explode('\\', $fullyQualifiedName);
384+
385+
return end($parts);
386+
}
377387
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace ApiPlatform\Tests\Fixtures\TestBundle\Dto;
15+
16+
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\WrappedResponseEntity;
17+
use Symfony\Component\Serializer\Annotation\Groups;
18+
19+
class CustomOutputEntityWrapperDto
20+
{
21+
/** @var WrappedResponseEntity */
22+
#[Groups(['read'])]
23+
public $data;
24+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace ApiPlatform\Tests\Fixtures\TestBundle\Entity;
15+
16+
use ApiPlatform\Metadata\ApiResource;
17+
use ApiPlatform\Metadata\Get;
18+
use ApiPlatform\Tests\Fixtures\TestBundle\Dto\CustomOutputEntityWrapperDto;
19+
use Doctrine\ORM\Mapping as ORM;
20+
use Symfony\Component\Serializer\Annotation\Groups;
21+
22+
#[ApiResource(operations: [new Get(normalizationContext: ['groups' => ['read']], output: CustomOutputEntityWrapperDto::class
23+
)])]
24+
#[ORM\Entity]
25+
class WrappedResponseEntity
26+
{
27+
#[ORM\Id]
28+
#[ORM\Column(type: 'guid')]
29+
#[Groups(['read'])]
30+
public $id;
31+
}

0 commit comments

Comments
 (0)