Skip to content

Commit

Permalink
Merge 3.4
Browse files Browse the repository at this point in the history
  • Loading branch information
soyuka committed Sep 13, 2024
2 parents fd50f8b + cb3aa86 commit d6e9f57
Show file tree
Hide file tree
Showing 10 changed files with 180 additions and 67 deletions.
5 changes: 5 additions & 0 deletions features/main/exception_to_status.feature
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,8 @@ Feature: Using exception_to_status config
And I send a "GET" request to "/issue5924"
Then the response status code should be 429
Then the header "retry-after" should be equal to 32

Scenario: Show error page
When I add "Accept" header equal to "text/html"
And I send a "GET" request to "/errors/404"
Then the response status code should be 200
Empty file.
2 changes: 1 addition & 1 deletion src/State/Provider/DeserializeProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ public function provide(Operation $operation, array $uriVariables = [], array $c
}

try {
return $this->serializer->deserialize((string) $request->getContent(), $operation->getClass(), $format, $serializerContext);
return $this->serializer->deserialize((string) $request->getContent(), $serializerContext['deserializer_type'] ?? $operation->getClass(), $format, $serializerContext);
} catch (PartialDenormalizationException $e) {
if (!class_exists(ConstraintViolationList::class)) {
throw $e;
Expand Down
1 change: 1 addition & 0 deletions src/State/SerializerContextBuilderInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ interface SerializerContextBuilderInterface
* exclude_from_cache_key?: string[],
* api_included?: bool,
* attributes?: string[],
* deserializer_type?: string,
* }
*/
public function createFromRequest(Request $request, bool $normalization, ?array $extractedAttributes = null): array;
Expand Down
131 changes: 131 additions & 0 deletions src/State/Tests/Provider/DeserializeProviderTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
<?php

/*
* This file is part of the API Platform project.
*
* (c) Kévin Dunglas <dunglas@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace ApiPlatform\State\Tests\Provider;

use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\Post;
use ApiPlatform\State\Provider\DeserializeProvider;
use ApiPlatform\State\ProviderInterface;
use ApiPlatform\State\SerializerContextBuilderInterface;
use PHPUnit\Framework\TestCase;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\UnsupportedMediaTypeHttpException;
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
use Symfony\Component\Serializer\SerializerInterface;

class DeserializeProviderTest extends TestCase
{
public function testDeserialize(): void
{
$objectToPopulate = new \stdClass();
$serializerContext = [];
$operation = new Post(deserialize: true, class: 'Test');
$decorated = $this->createStub(ProviderInterface::class);
$decorated->method('provide')->willReturn($objectToPopulate);

$serializerContextBuilder = $this->createMock(SerializerContextBuilderInterface::class);
$serializerContextBuilder->expects($this->once())->method('createFromRequest')->willReturn($serializerContext);
$serializer = $this->createMock(SerializerInterface::class);
$serializer->expects($this->once())->method('deserialize')->with('test', 'Test', 'format', ['uri_variables' => ['id' => 1], AbstractNormalizer::OBJECT_TO_POPULATE => $objectToPopulate] + $serializerContext)->willReturn(new \stdClass());

$provider = new DeserializeProvider($decorated, $serializer, $serializerContextBuilder);
$request = new Request(content: 'test');
$request->headers->set('CONTENT_TYPE', 'ok');
$request->attributes->set('input_format', 'format');
$provider->provide($operation, ['id' => 1], ['request' => $request]);
}

public function testDeserializeNoContentType(): void
{
$this->expectException(UnsupportedMediaTypeHttpException::class);
$operation = new Get(deserialize: true, class: 'Test');
$decorated = $this->createStub(ProviderInterface::class);
$decorated->method('provide')->willReturn(null);

$serializerContextBuilder = $this->createMock(SerializerContextBuilderInterface::class);
$serializer = $this->createMock(SerializerInterface::class);

$provider = new DeserializeProvider($decorated, $serializer, $serializerContextBuilder);
$request = new Request(content: 'test');
$request->attributes->set('input_format', 'format');
$provider->provide($operation, ['id' => 1], ['request' => $request]);
}

public function testDeserializeNoInput(): void
{
$this->expectException(UnsupportedMediaTypeHttpException::class);
$operation = new Get(deserialize: true, class: 'Test');
$decorated = $this->createStub(ProviderInterface::class);
$decorated->method('provide')->willReturn(null);

$serializerContextBuilder = $this->createMock(SerializerContextBuilderInterface::class);
$serializer = $this->createMock(SerializerInterface::class);

$provider = new DeserializeProvider($decorated, $serializer, $serializerContextBuilder);
$request = new Request(content: 'test');
$request->headers->set('CONTENT_TYPE', 'ok');
$provider->provide($operation, ['id' => 1], ['request' => $request]);
}

public function testDeserializeWithContextClass(): void
{
$serializerContext = ['deserializer_type' => 'Test'];
$operation = new Get(deserialize: true);
$decorated = $this->createStub(ProviderInterface::class);
$decorated->method('provide')->willReturn(null);

$serializerContextBuilder = $this->createMock(SerializerContextBuilderInterface::class);
$serializerContextBuilder->expects($this->once())->method('createFromRequest')->willReturn($serializerContext);
$serializer = $this->createMock(SerializerInterface::class);
$serializer->expects($this->once())->method('deserialize')->with('test', 'Test', 'format', ['uri_variables' => ['id' => 1]] + $serializerContext)->willReturn(new \stdClass());

$provider = new DeserializeProvider($decorated, $serializer, $serializerContextBuilder);
$request = new Request(content: 'test');
$request->headers->set('CONTENT_TYPE', 'ok');
$request->attributes->set('input_format', 'format');
$provider->provide($operation, ['id' => 1], ['request' => $request]);
}

public function testRequestWithEmptyContentType(): void
{
$expectedResult = new \stdClass();
$decorated = $this->createMock(ProviderInterface::class);
$decorated->method('provide')->willReturn($expectedResult);

$serializer = $this->createStub(SerializerInterface::class);
$serializerContextBuilder = $this->createStub(SerializerContextBuilderInterface::class);

$provider = new DeserializeProvider($decorated, $serializer, $serializerContextBuilder);

// in Symfony (at least up to 7.0.2, 6.4.2, 6.3.11, 5.4.34), a request
// without a content-type and content-length header will result in the
// variables set to an empty string, not null

$request = new Request(
server: [
'REQUEST_METHOD' => 'POST',
'REQUEST_URI' => '/',
'CONTENT_TYPE' => '',
'CONTENT_LENGTH' => '',
],
content: ''
);

$operation = new Post(deserialize: true);
$context = ['request' => $request];

$this->expectException(UnsupportedMediaTypeHttpException::class);
$provider->provide($operation, [], $context);
}
}
3 changes: 2 additions & 1 deletion src/State/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@
"require": {
"php": ">=8.1",
"api-platform/metadata": "^3.4 || ^4.0",
"psr/container": "^1.0 || ^2.0"
"psr/container": "^1.0 || ^2.0",
"symfony/http-kernel": "^6.4 || 7.0"
},
"require-dev": {
"phpunit/phpunit": "^11.2",
Expand Down
38 changes: 38 additions & 0 deletions src/Symfony/Action/ErrorPageAction.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

/*
* This file is part of the API Platform project.
*
* (c) Kévin Dunglas <dunglas@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace ApiPlatform\Symfony\Action;

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;

final class ErrorPageAction
{
public function __invoke(Request $request): Response
{
$status = $request->attributes->get('status');
$text = Response::$statusTexts[$status] ?? throw new NotFoundHttpException();

return new Response(<<<HTML
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Error $status</title>
</head>
<body><h1>Error $status</h1>$text</body>
</html>
HTML);
}
}
1 change: 1 addition & 0 deletions src/Symfony/Bundle/Resources/config/api.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
<service id="api_platform.property_accessor" alias="property_accessor" public="false" />
<service id="api_platform.property_info" alias="property_info" public="false" />
<service id="api_platform.negotiator" class="Negotiation\Negotiator" public="false" />
<service id="api_platform.action.error_page" class="ApiPlatform\Symfony\Action\ErrorPageAction" public="true" />

<service id="api_platform.resource_class_resolver" class="ApiPlatform\Metadata\ResourceClassResolver" public="false">
<argument type="service" id="api_platform.metadata.resource.name_collection_factory" />
Expand Down
3 changes: 1 addition & 2 deletions src/Symfony/Bundle/Resources/config/routing/errors.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@
http://symfony.com/schema/routing/routing-1.0.xsd">

<route id="api_errors" path="/errors/{status}" methods="GET|HEAD">
<default key="_controller">api_platform.action.not_exposed</default>
<default key="status">500</default>
<default key="_controller">api_platform.action.error_page</default>

<requirement key="status">\d+</requirement>
</route>
Expand Down
63 changes: 0 additions & 63 deletions tests/State/Provider/DeserializeProviderTest.php

This file was deleted.

0 comments on commit d6e9f57

Please sign in to comment.