Skip to content

Commit e471622

Browse files
authored
fix(jsonschema): find the related operation instead of assuming one (#5469)
1 parent dd94591 commit e471622

File tree

5 files changed

+118
-26
lines changed

5 files changed

+118
-26
lines changed

features/openapi/docs.feature

+21
Original file line numberDiff line numberDiff line change
@@ -299,3 +299,24 @@ Feature: Documentation support
299299
And the JSON node "components.schemas.RamseyUuidDummy.properties.id.description" should be equal to "The dummy id."
300300
And the JSON node "components.schemas.RelatedDummy-barcelona" should not exist
301301
And the JSON node "components.schemas.RelatedDummybarcelona" should exist
302+
303+
@!mongodb
304+
Scenario: Retrieve the OpenAPI documentation to see if shortName property is used
305+
Given I send a "GET" request to "/docs.json"
306+
Then the response status code should be 200
307+
And the response should be in JSON
308+
And the header "Content-Type" should be equal to "application/json; charset=utf-8"
309+
And the OpenAPI class "Resource" exists
310+
And the OpenAPI class "ResourceRelated" exists
311+
And the "resourceRelated" property for the OpenAPI class "Resource" should be equal to:
312+
"""
313+
{
314+
"readOnly":true,
315+
"anyOf":[
316+
{
317+
"$ref":"#/components/schemas/ResourceRelated"
318+
}
319+
],
320+
"nullable":true
321+
}
322+
"""

src/JsonSchema/SchemaFactory.php

+38-22
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,15 @@
1414
namespace ApiPlatform\JsonSchema;
1515

1616
use ApiPlatform\Api\ResourceClassResolverInterface;
17+
use ApiPlatform\Exception\OperationNotFoundException;
1718
use ApiPlatform\Metadata\ApiProperty;
1819
use ApiPlatform\Metadata\CollectionOperationInterface;
1920
use ApiPlatform\Metadata\HttpOperation;
2021
use ApiPlatform\Metadata\Operation;
2122
use ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface;
2223
use ApiPlatform\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface;
2324
use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
25+
use ApiPlatform\Metadata\Resource\ResourceMetadataCollection;
2426
use ApiPlatform\OpenApi\Factory\OpenApiFactory;
2527
use ApiPlatform\Util\ResourceClassInfoTrait;
2628
use Symfony\Component\PropertyInfo\Type;
@@ -269,30 +271,24 @@ private function getMetadata(string $className, string $type = Schema::TYPE_OUTP
269271
];
270272
}
271273

272-
// The best here is to use an Operation when calling `buildSchema`, we try to do a smart guess otherwise
273-
if (!$operation || !$operation->getClass()) {
274+
if (null === $operation) {
274275
$resourceMetadataCollection = $this->resourceMetadataFactory->create($className);
276+
try {
277+
$operation = $resourceMetadataCollection->getOperation();
278+
} catch (OperationNotFoundException $e) {
279+
$operation = new HttpOperation();
280+
}
275281

276-
if ($operation && $operation->getName()) {
277-
$operation = $resourceMetadataCollection->getOperation($operation->getName());
278-
} else {
279-
// Guess the operation and use the first one that matches criterias
280-
foreach ($resourceMetadataCollection as $resourceMetadata) {
281-
foreach ($resourceMetadata->getOperations() ?? [] as $op) {
282-
if ($operation instanceof CollectionOperationInterface && $op instanceof CollectionOperationInterface) {
283-
$operation = $op;
284-
break 2;
285-
}
286-
287-
if (Schema::TYPE_INPUT === $type && \in_array($op->getMethod(), ['POST', 'PATCH', 'PUT'], true)) {
288-
$operation = $op;
289-
break 2;
290-
}
291-
292-
if (!$operation) {
293-
$operation = new HttpOperation();
294-
}
295-
}
282+
$operation = $this->findOperationForType($resourceMetadataCollection, $type, $operation);
283+
} else {
284+
// The best here is to use an Operation when calling `buildSchema`, we try to do a smart guess otherwise
285+
if (!$operation->getClass()) {
286+
$resourceMetadataCollection = $this->resourceMetadataFactory->create($className);
287+
288+
if ($operation->getName()) {
289+
$operation = $resourceMetadataCollection->getOperation($operation->getName());
290+
} else {
291+
$operation = $this->findOperationForType($resourceMetadataCollection, $type, $operation);
296292
}
297293
}
298294
}
@@ -320,6 +316,26 @@ private function getMetadata(string $className, string $type = Schema::TYPE_OUTP
320316
];
321317
}
322318

319+
private function findOperationForType(ResourceMetadataCollection $resourceMetadataCollection, string $type, Operation $operation)
320+
{
321+
// Find the operation and use the first one that matches criterias
322+
foreach ($resourceMetadataCollection as $resourceMetadata) {
323+
foreach ($resourceMetadata->getOperations() ?? [] as $op) {
324+
if ($operation instanceof CollectionOperationInterface && $op instanceof CollectionOperationInterface) {
325+
$operation = $op;
326+
break 2;
327+
}
328+
329+
if (Schema::TYPE_INPUT === $type && \in_array($op->getMethod(), ['POST', 'PATCH', 'PUT'], true)) {
330+
$operation = $op;
331+
break 2;
332+
}
333+
}
334+
}
335+
336+
return $operation;
337+
}
338+
323339
private function getSerializerContext(Operation $operation, string $type = Schema::TYPE_OUTPUT): array
324340
{
325341
return Schema::TYPE_OUTPUT === $type ? ($operation->getNormalizationContext() ?? []) : ($operation->getDenormalizationContext() ?? []);

src/Metadata/Resource/ResourceMetadataCollection.php

+4-4
Original file line numberDiff line numberDiff line change
@@ -89,9 +89,9 @@ public function getOperation(?string $operationName = null, bool $forceCollectio
8989
}
9090

9191
// Idea:
92-
// if ($metadata) {
93-
// return (new class extends HttpOperation {})->withResource($metadata);
94-
// }
92+
// if ($metadata) {
93+
// return (new class extends HttpOperation {})->withResource($metadata);
94+
// }
9595

9696
$this->handleNotFound($operationName, $metadata);
9797
}
@@ -102,7 +102,7 @@ public function getOperation(?string $operationName = null, bool $forceCollectio
102102
private function handleNotFound(string $operationName, ?ApiResource $metadata): void
103103
{
104104
// Hide the FQDN in the exception message if possible
105-
$shortName = $metadata ? $metadata->getShortName() : $this->resourceClass;
105+
$shortName = $metadata?->getShortName() ? $metadata->getShortName() : $this->resourceClass;
106106
if (!$metadata && false !== $pos = strrpos($shortName, '\\')) {
107107
$shortName = substr($shortName, $pos + 1);
108108
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
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\ApiProperty;
17+
use ApiPlatform\Metadata\ApiResource;
18+
19+
#[ApiResource(
20+
shortName: 'Resource',
21+
)]
22+
class JsonSchemaResource
23+
{
24+
#[ApiProperty(identifier: true)]
25+
public $id;
26+
27+
#[ApiProperty(writable: false, readableLink: true)]
28+
public ?JsonSchemaResourceRelated $resourceRelated = null;
29+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
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\ApiProperty;
17+
use ApiPlatform\Metadata\ApiResource;
18+
19+
#[ApiResource(
20+
shortName: 'ResourceRelated',
21+
)]
22+
class JsonSchemaResourceRelated
23+
{
24+
#[ApiProperty(identifier: true)]
25+
public $id;
26+
}

0 commit comments

Comments
 (0)