Skip to content

Commit 158ea5c

Browse files
committed
Merge 3.3
2 parents 7399fcf + 84df467 commit 158ea5c

File tree

14 files changed

+418
-6
lines changed

14 files changed

+418
-6
lines changed

CHANGELOG.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,27 @@
11
# Changelog
22

3+
## v3.3.8
4+
5+
### Bug fixes
6+
7+
* [0edc73806](https://github.com/api-platform/core/commit/0edc73806c047b04c452a1cd7bc1132106906d02) fix(state): allow to skip parameter validator provider (#6452)
8+
* [25c5f222d](https://github.com/api-platform/core/commit/25c5f222dbe24ff111d2ba2b19556112a50ed87a) fix(graphql): security after resolver (#6444)
9+
* [32ef3d4e2](https://github.com/api-platform/core/commit/32ef3d4e24f14b361e2c67efb2494b8494a87b12) fix(jsonld): allow @id, @context and @type on denormalization 2 (#6451)
10+
* [4de43bab9](https://github.com/api-platform/core/commit/4de43bab9fbcafcd79ceac9bbe3a1293b56303c4) fix: swagger ui provider accept html (#6449)
11+
* [65ac0d228](https://github.com/api-platform/core/commit/65ac0d22885c8c9b68df9f3becd1b004746ce019) fix(symfony): property info with doctrine collections
12+
* [7926dc68d](https://github.com/api-platform/core/commit/7926dc68d1d0e9df7ff6439487beb782dd124671) fix: parameter not found when no value (#6458)
13+
* [93e4b3d70](https://github.com/api-platform/core/commit/93e4b3d709a6a9bbfb4fd0722f7aa5b01efa57d7) fix(state): store parameter value without its key (#6456)
14+
* [c086dfe9d](https://github.com/api-platform/core/commit/c086dfe9dd516e5fa26e1a819ba0477c50b4ffda) fix(openapi): optional yaml component (#6445)
15+
* [c473b2efe](https://github.com/api-platform/core/commit/c473b2efec4e53929fbe2832452894d489002455) fix(state): query and header parameter with the same name (#6453)
16+
17+
18+
### Features
19+
20+
These are enhancement to the experimental Parameter feature:
21+
22+
* [9ac50d294](https://github.com/api-platform/core/commit/9ac50d294f95a0c34baf67aebf3ea5623bc1be4c) feat(state): review validation for required parameters (#6441)
23+
* [c2d3aeb91](https://github.com/api-platform/core/commit/c2d3aeb91764e5fa1f8f56de3a800cc69364a9b0) feat(state): list all violations during query parameters validation (#6442)
24+
325
## v3.3.7
426

527
### Bug fixes

features/main/standard_put.feature

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,60 @@ Feature: Spec-compliant PUT support
2626
}
2727
"""
2828

29+
Scenario: Create a new resource with JSON-LD attributes
30+
When I add "Content-Type" header equal to "application/ld+json"
31+
And I send a "PUT" request to "/standard_puts/6" with body:
32+
"""
33+
{
34+
"@id": "/standard_puts/6",
35+
"@context": "/contexts/StandardPut",
36+
"@type": "StandardPut",
37+
"foo": "a",
38+
"bar": "b"
39+
}
40+
"""
41+
Then the response status code should be 201
42+
And the response should be in JSON
43+
And the JSON should be equal to:
44+
"""
45+
{
46+
"@context": "/contexts/StandardPut",
47+
"@id": "/standard_puts/6",
48+
"@type": "StandardPut",
49+
"id": 6,
50+
"foo": "a",
51+
"bar": "b"
52+
}
53+
"""
54+
55+
Scenario: Fails to create a new resource with the wrong JSON-LD @id
56+
When I add "Content-Type" header equal to "application/ld+json"
57+
And I send a "PUT" request to "/standard_puts/7" with body:
58+
"""
59+
{
60+
"@id": "/dummies/6",
61+
"@context": "/contexts/StandardPut",
62+
"@type": "StandardPut",
63+
"foo": "a",
64+
"bar": "b"
65+
}
66+
"""
67+
Then the response status code should be 400
68+
69+
Scenario: Fails to create a new resource when the JSON-LD @id doesn't match the URI
70+
When I add "Content-Type" header equal to "application/ld+json"
71+
And I send a "PUT" request to "/standard_puts/7" with body:
72+
"""
73+
{
74+
"@id": "/standard_puts/6",
75+
"@context": "/contexts/StandardPut",
76+
"@type": "StandardPut",
77+
"foo": "a",
78+
"bar": "b"
79+
}
80+
"""
81+
Then the response status code should be 400
82+
2983
Scenario: Replace an existing resource
3084
When I add "Content-Type" header equal to "application/ld+json"
3185
And I send a "PUT" request to "/standard_puts/5" with body:

src/JsonLd/Serializer/ItemNormalizer.php

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
use ApiPlatform\Api\ResourceClassResolverInterface as LegacyResourceClassResolverInterface;
1818
use ApiPlatform\JsonLd\AnonymousContextBuilderInterface;
1919
use ApiPlatform\JsonLd\ContextBuilderInterface;
20+
use ApiPlatform\Metadata\Exception\ItemNotFoundException;
2021
use ApiPlatform\Metadata\HttpOperation;
2122
use ApiPlatform\Metadata\IriConverterInterface;
2223
use ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface;
@@ -47,6 +48,29 @@ final class ItemNormalizer extends AbstractItemNormalizer
4748
use JsonLdContextTrait;
4849

4950
public const FORMAT = 'jsonld';
51+
private const JSONLD_KEYWORDS = [
52+
'@context',
53+
'@direction',
54+
'@graph',
55+
'@id',
56+
'@import',
57+
'@included',
58+
'@index',
59+
'@json',
60+
'@language',
61+
'@list',
62+
'@nest',
63+
'@none',
64+
'@prefix',
65+
'@propagate',
66+
'@protected',
67+
'@reverse',
68+
'@set',
69+
'@type',
70+
'@value',
71+
'@version',
72+
'@vocab',
73+
];
5074

5175
public function __construct(ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory, PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, IriConverterInterface|LegacyIriConverterInterface $iriConverter, ResourceClassResolverInterface|LegacyResourceClassResolverInterface $resourceClassResolver, private readonly ContextBuilderInterface $contextBuilder, ?PropertyAccessorInterface $propertyAccessor = null, ?NameConverterInterface $nameConverter = null, ?ClassMetadataFactoryInterface $classMetadataFactory = null, array $defaultContext = [], ?ResourceAccessCheckerInterface $resourceAccessChecker = null, protected ?TagCollectorInterface $tagCollector = null)
5276
{
@@ -148,9 +172,27 @@ public function denormalize(mixed $data, string $class, ?string $format = null,
148172
throw new NotNormalizableValueException('Update is not allowed for this operation.');
149173
}
150174

151-
$context[self::OBJECT_TO_POPULATE] = $this->iriConverter->getResourceFromIri($data['@id'], $context + ['fetch_data' => true]);
175+
try {
176+
$context[self::OBJECT_TO_POPULATE] = $this->iriConverter->getResourceFromIri($data['@id'], $context + ['fetch_data' => true], $context['operation'] ?? null);
177+
} catch (ItemNotFoundException $e) {
178+
$operation = $context['operation'] ?? null;
179+
180+
if (!('PUT' === $operation?->getMethod() && ($operation->getExtraProperties()['standard_put'] ?? false))) {
181+
throw $e;
182+
}
183+
}
152184
}
153185

154186
return parent::denormalize($data, $class, $format, $context);
155187
}
188+
189+
protected function getAllowedAttributes(string|object $classOrObject, array $context, bool $attributesAsString = false): array|bool
190+
{
191+
$allowedAttributes = parent::getAllowedAttributes($classOrObject, $context, $attributesAsString);
192+
if (\is_array($allowedAttributes) && ($context['api_denormalize'] ?? false)) {
193+
$allowedAttributes = array_merge($allowedAttributes, self::JSONLD_KEYWORDS);
194+
}
195+
196+
return $allowedAttributes;
197+
}
156198
}

src/Serializer/AbstractItemNormalizer.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ public function denormalize(mixed $data, string $class, ?string $format = null,
220220
throw new LogicException('Cannot denormalize the input because the injected serializer is not a denormalizer');
221221
}
222222

223-
unset($context['input'], $context['operation'], $context['operation_name']);
223+
unset($context['input'], $context['operation'], $context['operation_name'], $context['uri_variables']);
224224
$context['resource_class'] = $inputClass;
225225

226226
try {

src/Symfony/Bundle/SwaggerUi/SwaggerUiProvider.php

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
namespace ApiPlatform\Symfony\Bundle\SwaggerUi;
1515

1616
use ApiPlatform\Documentation\Documentation;
17+
use ApiPlatform\Documentation\Entrypoint;
1718
use ApiPlatform\Metadata\Error;
1819
use ApiPlatform\Metadata\Get;
1920
use ApiPlatform\Metadata\HttpOperation;
@@ -44,6 +45,7 @@ public function provide(Operation $operation, array $uriVariables = [], array $c
4445
!($operation instanceof HttpOperation)
4546
|| !($request = $context['request'] ?? null)
4647
|| 'html' !== $request->getRequestFormat()
48+
|| true === ($operation->getExtraProperties()['_api_disable_swagger_provider'] ?? false)
4749
) {
4850
return $this->decorated->provide($operation, $uriVariables, $context);
4951
}
@@ -55,11 +57,12 @@ public function provide(Operation $operation, array $uriVariables = [], array $c
5557
// We need to call our operation provider just in case it fails
5658
// when it fails we'll get an Error and we'll fix the status accordingly
5759
// @see features/main/content_negotiation.feature:119
58-
// DocumentationAction has no content negotation as well we want HTML so render swagger ui
59-
if (!$operation instanceof Error && Documentation::class !== $operation->getClass()) {
60+
// When requesting DocumentationAction or EntrypointAction with Accept: text/html we render SwaggerUi
61+
if (!$operation instanceof Error && !\in_array($operation->getClass(), [Documentation::class, Entrypoint::class], true)) {
6062
$this->decorated->provide($operation, $uriVariables, $context);
6163
}
6264

65+
// This should render only when an error occured
6366
$swaggerUiOperation = new Get(
6467
class: OpenApi::class,
6568
processor: 'api_platform.swagger_ui.processor',
@@ -71,7 +74,6 @@ class: OpenApi::class,
7174

7275
// save our operation
7376
$request->attributes->set('_api_operation', $swaggerUiOperation);
74-
7577
$data = $this->openApiFactory->__invoke(['base_url' => $request->getBaseUrl() ?: '/']);
7678
$request->attributes->set('data', $data);
7779

src/Symfony/Routing/IriConverter.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,17 @@ public function getResourceFromIri(string $iri, array $context = [], ?Operation
7878
throw new InvalidArgumentException(sprintf('No resource associated to "%s".', $iri));
7979
}
8080

81+
// uri_variables come from the Request context and may not be available
82+
foreach ($context['uri_variables'] ?? [] as $key => $value) {
83+
if (!isset($parameters[$key]) || $parameters[$key] !== (string) $value) {
84+
throw new InvalidArgumentException(sprintf('The iri "%s" does not reference the correct resource.', $iri));
85+
}
86+
}
87+
88+
if ($operation && !is_a($parameters['_api_resource_class'], $operation->getClass(), true)) {
89+
throw new InvalidArgumentException(sprintf('The iri "%s" does not reference the correct resource.', $iri));
90+
}
91+
8192
$operation = $parameters['_api_operation'] = $this->resourceMetadataCollectionFactory->create($parameters['_api_resource_class'])->getOperation($parameters['_api_operation_name']);
8293

8394
if ($operation instanceof CollectionOperationInterface) {
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
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\ApiResource\Issue6384;
15+
16+
use ApiPlatform\Metadata\Get;
17+
use Symfony\Component\HttpFoundation\Response;
18+
19+
#[Get(
20+
uriTemplate: 'accept_html',
21+
provider: [self::class, 'provide'],
22+
outputFormats: ['html' => ['text/html']],
23+
formats: ['html' => ['text/html']],
24+
extraProperties: ['_api_disable_swagger_provider' => true]
25+
)]
26+
class AcceptHtml
27+
{
28+
public static function provide(): Response
29+
{
30+
return new Response('<h1>hello</h1>');
31+
}
32+
}
Lines changed: 31 additions & 0 deletions
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\Issue6465;
15+
16+
use ApiPlatform\Metadata\ApiResource;
17+
use Doctrine\ORM\Mapping as ORM;
18+
19+
#[ORM\Entity()]
20+
#[ApiResource(shortName: 'Bar6465')]
21+
#[ORM\Table(name: 'bar6465')]
22+
class Bar
23+
{
24+
#[ORM\Id]
25+
#[ORM\GeneratedValue]
26+
#[ORM\Column]
27+
public ?int $id = null;
28+
29+
#[ORM\Column]
30+
public string $title = '';
31+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
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\Issue6465;
15+
16+
use Symfony\Component\Validator\Constraints\NotBlank;
17+
18+
class CustomInput
19+
{
20+
#[NotBlank]
21+
public Bar $bar;
22+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
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\Issue6465;
15+
16+
class CustomOutput
17+
{
18+
public function __construct(public string $title)
19+
{
20+
}
21+
}

0 commit comments

Comments
 (0)