Skip to content

Commit 17d92a7

Browse files
soyukaalanpoulain
andauthored
Fix #4037 allow POST operations without identifiers (#4052)
* Fix #4037 allow POST operations without identifiers * Better * remove line * Update features/main/overridden_operation.feature Co-authored-by: Alan Poulain <[email protected]> * fix review * Fix mongodb Co-authored-by: Alan Poulain <[email protected]>
1 parent 39167de commit 17d92a7

File tree

12 files changed

+184
-6
lines changed

12 files changed

+184
-6
lines changed

features/main/overridden_operation.feature

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,3 +130,27 @@ Feature: Create-Retrieve-Update-Delete with a Overridden Operation context
130130
When I send a "DELETE" request to "/overridden_operation_dummies/1"
131131
Then the response status code should be 204
132132
And the response should be empty
133+
134+
@createSchema
135+
Scenario: Use a POST operation to do a Remote Procedure Call without identifiers
136+
When I add "Content-Type" header equal to "application/json"
137+
And I send a "POST" request to "/rpc"
138+
"""
139+
{
140+
"value": "Hello world"
141+
}
142+
"""
143+
Then the response status code should be 202
144+
145+
@createSchema
146+
Scenario: Use a POST operation to do a Remote Procedure Call without identifiers and with an output DTO
147+
When I add "Content-Type" header equal to "application/json"
148+
And I send a "POST" request to "/rpc_output"
149+
"""
150+
{
151+
"value": "Hello world"
152+
}
153+
"""
154+
Then the response status code should be 200
155+
And the JSON node "success" should be equal to "YES"
156+
And the JSON node "@type" should be equal to "RPC"

src/Api/IdentifiersExtractor.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ public function getIdentifiersFromResourceClass(string $resourceClass): array
6262
return ['id'];
6363
}
6464

65-
throw new RuntimeException(sprintf('No identifier defined "%s". You should add #[\ApiPlatform\Core\Annotation\ApiProperty(identifier: true)]" on the property identifying the resource."', $resourceClass));
65+
throw new RuntimeException(sprintf('No identifier defined in "%s". You should add #[\ApiPlatform\Core\Annotation\ApiProperty(identifier: true)]" on the property identifying the resource."', $resourceClass));
6666
}
6767

6868
return $identifiers;

src/Bridge/Symfony/Routing/ApiLoader.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,12 @@ private function addRoute(RouteCollection $routeCollection, string $resourceClas
227227
}
228228
}
229229

230-
$operation['identifiers'] = (array) ($operation['identifiers'] ?? $resourceMetadata->getAttribute('identifiers', $this->identifiersExtractor ? $this->identifiersExtractor->getIdentifiersFromResourceClass($resourceClass) : ['id']));
230+
if ($resourceMetadata->getItemOperations()) {
231+
$operation['identifiers'] = (array) ($operation['identifiers'] ?? $resourceMetadata->getAttribute('identifiers', $this->identifiersExtractor ? $this->identifiersExtractor->getIdentifiersFromResourceClass($resourceClass) : ['id']));
232+
} else {
233+
$operation['identifiers'] = $operation['identifiers'] ?? [];
234+
}
235+
231236
$operation['has_composite_identifier'] = \count($operation['identifiers']) > 1 ? $resourceMetadata->getAttribute('composite_identifier', true) : false;
232237
$path = trim(trim($resourceMetadata->getAttribute('route_prefix', '')), '/');
233238
$path .= $this->operationPathResolver->resolveOperationPath($resourceShortName, $operation, $operationType, $operationName);

src/JsonLd/Serializer/ObjectNormalizer.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
namespace ApiPlatform\Core\JsonLd\Serializer;
1515

1616
use ApiPlatform\Core\Api\IriConverterInterface;
17+
use ApiPlatform\Core\Exception\InvalidArgumentException;
1718
use ApiPlatform\Core\JsonLd\AnonymousContextBuilderInterface;
1819
use ApiPlatform\Core\Metadata\Property\PropertyMetadata;
1920
use Symfony\Component\Serializer\Normalizer\CacheableSupportsMethodInterface;
@@ -84,7 +85,11 @@ public function normalize($object, $format = null, array $context = [])
8485
}
8586

8687
if (isset($originalResource)) {
87-
$context['output']['iri'] = $this->iriConverter->getIriFromItem($originalResource);
88+
try {
89+
$context['output']['iri'] = $this->iriConverter->getIriFromItem($originalResource);
90+
} catch (InvalidArgumentException $e) {
91+
// The original resource has no identifiers
92+
}
8893
$context['api_resource'] = $originalResource;
8994
}
9095

src/OpenApi/Factory/OpenApiFactory.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,11 @@ private function collectPaths(ResourceMetadata $resourceMetadata, string $resour
138138

139139
$rootResourceClass = $resourceClass;
140140
foreach ($operations as $operationName => $operation) {
141-
$identifiers = (array) ($operation['identifiers'] ?? $resourceMetadata->getAttribute('identifiers', null === $this->identifiersExtractor ? ['id'] : $this->identifiersExtractor->getIdentifiersFromResourceClass($resourceClass)));
141+
if (OperationType::COLLECTION === $operationType && !$resourceMetadata->getItemOperations()) {
142+
$identifiers = [];
143+
} else {
144+
$identifiers = (array) ($operation['identifiers'] ?? $resourceMetadata->getAttribute('identifiers', null === $this->identifiersExtractor ? ['id'] : $this->identifiersExtractor->getIdentifiersFromResourceClass($resourceClass)));
145+
}
142146
if (\count($identifiers) > 1 ? $resourceMetadata->getAttribute('composite_identifier', true) : false) {
143147
$identifiers = ['id'];
144148
}

src/Swagger/Serializer/DocumentationNormalizer.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,12 @@ public function normalize($object, $format = null, array $context = [])
197197
foreach ($object->getResourceNameCollection() as $resourceClass) {
198198
$resourceMetadata = $this->resourceMetadataFactory->create($resourceClass);
199199
if ($this->identifiersExtractor) {
200-
$resourceMetadata = $resourceMetadata->withAttributes(($resourceMetadata->getAttributes() ?: []) + ['identifiers' => $this->identifiersExtractor->getIdentifiersFromResourceClass($resourceClass)]);
200+
$identifiers = [];
201+
if ($resourceMetadata->getItemOperations()) {
202+
$identifiers = $this->identifiersExtractor->getIdentifiersFromResourceClass($resourceClass);
203+
}
204+
205+
$resourceMetadata = $resourceMetadata->withAttributes(($resourceMetadata->getAttributes() ?: []) + ['identifiers' => $identifiers]);
201206
}
202207
$resourceShortName = $resourceMetadata->getShortName();
203208

tests/Api/IdentifiersExtractorTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,7 @@ public function testGetsIdentifiersFromCorrectResourceClass(): void
205205
public function testNoIdentifiers(): void
206206
{
207207
$this->expectException(RuntimeException::class);
208-
$this->expectExceptionMessage('No identifier defined "ApiPlatform\\Core\\Tests\\Fixtures\\TestBundle\\Entity\\Dummy". You should add #[\ApiPlatform\Core\Annotation\ApiProperty(identifier: true)]" on the property identifying the resource.');
208+
$this->expectExceptionMessage('No identifier defined in "ApiPlatform\\Core\\Tests\\Fixtures\\TestBundle\\Entity\\Dummy". You should add #[\ApiPlatform\Core\Annotation\ApiProperty(identifier: true)]" on the property identifying the resource.');
209209
$propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
210210
$propertyNameCollectionFactoryProphecy->create(Dummy::class)->willReturn(new PropertyNameCollection(['foo']));
211211
$propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
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\Core\Tests\Fixtures\TestBundle\DataTransformer;
15+
16+
use ApiPlatform\Core\DataTransformer\DataTransformerInterface;
17+
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Dto\RPCOutput;
18+
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\RPC;
19+
20+
final class RPCOutputDataTransformer implements DataTransformerInterface
21+
{
22+
/**
23+
* {@inheritdoc}
24+
*/
25+
public function transform($object, string $to, array $context = [])
26+
{
27+
return new RPCOutput();
28+
}
29+
30+
/**
31+
* {@inheritdoc}
32+
*/
33+
public function supportsTransformation($object, string $to, array $context = []): bool
34+
{
35+
return $object instanceof RPC && RPCOutput::class === $to;
36+
}
37+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
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\Core\Tests\Fixtures\TestBundle\Document;
15+
16+
use ApiPlatform\Core\Annotation\ApiResource;
17+
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Dto\RPCOutput;
18+
19+
/**
20+
* RPC-like resource.
21+
*
22+
* @ApiResource(
23+
* itemOperations={},
24+
* collectionOperations={
25+
* "post"={"status"=202, "messenger"=true, "path"="rpc", "output"=false},
26+
* "post_output"={"method"="POST", "status"=200, "path"="rpc_output", "output"=RPCOutput::class}
27+
* },
28+
* )
29+
*/
30+
class RPC
31+
{
32+
/**
33+
* @var string
34+
*/
35+
public $value;
36+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
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\Core\Tests\Fixtures\TestBundle\Dto;
15+
16+
class RPCOutput
17+
{
18+
public $success = 'YES';
19+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
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\Core\Tests\Fixtures\TestBundle\Entity;
15+
16+
use ApiPlatform\Core\Annotation\ApiResource;
17+
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Dto\RPCOutput;
18+
19+
/**
20+
* RPC-like resource.
21+
*
22+
* @ApiResource(
23+
* itemOperations={},
24+
* collectionOperations={
25+
* "post"={"status"=202, "messenger"=true, "path"="rpc", "output"=false},
26+
* "post_output"={"method"="POST", "status"=200, "path"="rpc_output", "output"=RPCOutput::class}
27+
* },
28+
* )
29+
*/
30+
class RPC
31+
{
32+
/**
33+
* @var string
34+
*/
35+
public $value;
36+
}

tests/Fixtures/app/config/config_common.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,3 +371,10 @@ services:
371371
decorates: 'api_platform.graphql.type_converter'
372372
arguments: ['@app.graphql.type_converter.inner']
373373
public: false
374+
375+
app.data_transformer.rpc_output:
376+
class: 'ApiPlatform\Core\Tests\Fixtures\TestBundle\DataTransformer\RPCOutputDataTransformer'
377+
public: false
378+
tags:
379+
- { name: 'api_platform.data_transformer' }
380+

0 commit comments

Comments
 (0)