Upstream api-platform/core state of the touched files before 5e95dd3773
This commit is contained in:
1 parent
167625fa31
commit
b2a14e558d
5 files changed
+968
No files matched your search
@@ -0,0 +1,201 @@
|
||||
<?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\Provider;
|
||||
|
||||
use ApiPlatform\Metadata\HttpOperation;
|
||||
use ApiPlatform\Metadata\Operation;
|
||||
use ApiPlatform\State\ProviderInterface;
|
||||
use ApiPlatform\State\SerializerContextBuilderInterface;
|
||||
use ApiPlatform\State\StopwatchAwareInterface;
|
||||
use ApiPlatform\State\StopwatchAwareTrait;
|
||||
use ApiPlatform\Validator\Exception\ValidationException;
|
||||
use Symfony\Component\HttpKernel\Exception\UnsupportedMediaTypeHttpException;
|
||||
use Symfony\Component\Serializer\Exception\NotNormalizableValueException;
|
||||
use Symfony\Component\Serializer\Exception\PartialDenormalizationException;
|
||||
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
|
||||
use Symfony\Component\Serializer\SerializerInterface;
|
||||
use Symfony\Component\Validator\Constraints\Type;
|
||||
use Symfony\Component\Validator\ConstraintViolation;
|
||||
use Symfony\Component\Validator\ConstraintViolationList;
|
||||
use Symfony\Contracts\Translation\LocaleAwareInterface;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
use Symfony\Contracts\Translation\TranslatorTrait;
|
||||
|
||||
final class DeserializeProvider implements ProviderInterface, StopwatchAwareInterface
|
||||
{
|
||||
use StopwatchAwareTrait;
|
||||
|
||||
public function __construct(
|
||||
private readonly ?ProviderInterface $decorated,
|
||||
private readonly SerializerInterface $serializer,
|
||||
private readonly SerializerContextBuilderInterface $serializerContextBuilder,
|
||||
private ?TranslatorInterface $translator = null,
|
||||
) {
|
||||
if (null === $this->translator) {
|
||||
$this->translator = new class implements TranslatorInterface, LocaleAwareInterface {
|
||||
use TranslatorTrait;
|
||||
};
|
||||
$this->translator->setLocale('en');
|
||||
}
|
||||
}
|
||||
|
||||
public function provide(Operation $operation, array $uriVariables = [], array $context = []): object|array|null
|
||||
{
|
||||
// We need request content
|
||||
if (!$operation instanceof HttpOperation || !($request = $context['request'] ?? null)) {
|
||||
return $this->decorated?->provide($operation, $uriVariables, $context);
|
||||
}
|
||||
|
||||
$data = $this->decorated ? $this->decorated->provide($operation, $uriVariables, $context) : $request->attributes->get('data');
|
||||
|
||||
if (!$operation->canDeserialize() || $context['request']->attributes->has('deserialized')) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$this->stopwatch?->start('api_platform.provider.deserialize');
|
||||
|
||||
$contentType = $request->headers->get('CONTENT_TYPE');
|
||||
if (null === $contentType || '' === $contentType) {
|
||||
throw new UnsupportedMediaTypeHttpException('The "Content-Type" header must exist.');
|
||||
}
|
||||
|
||||
$serializerContext = $this->serializerContextBuilder->createFromRequest($request, false, [
|
||||
'resource_class' => $operation->getClass(),
|
||||
'operation' => $operation,
|
||||
]);
|
||||
|
||||
$serializerContext['uri_variables'] = $uriVariables;
|
||||
|
||||
if (!$format = $request->attributes->get('input_format') ?? null) {
|
||||
throw new UnsupportedMediaTypeHttpException('Format not supported.');
|
||||
}
|
||||
|
||||
if (null === ($serializerContext[SerializerContextBuilderInterface::ASSIGN_OBJECT_TO_POPULATE] ?? null)) {
|
||||
$method = $operation->getMethod();
|
||||
$assignObjectToPopulate = 'POST' === $method
|
||||
|| 'PATCH' === $method
|
||||
|| ('PUT' === $method && !($operation->getExtraProperties()['standard_put'] ?? true));
|
||||
|
||||
if ($assignObjectToPopulate) {
|
||||
$serializerContext[SerializerContextBuilderInterface::ASSIGN_OBJECT_TO_POPULATE] = true;
|
||||
trigger_deprecation('api-platform/core', '4.4', 'To assign an object to populate you should set "%s" in your denormalizationContext, not defining it is deprecated.', SerializerContextBuilderInterface::ASSIGN_OBJECT_TO_POPULATE);
|
||||
}
|
||||
}
|
||||
|
||||
if (null !== $data && ($serializerContext[SerializerContextBuilderInterface::ASSIGN_OBJECT_TO_POPULATE] ?? false)) {
|
||||
$serializerContext[AbstractNormalizer::OBJECT_TO_POPULATE] = $data;
|
||||
}
|
||||
|
||||
unset($serializerContext[SerializerContextBuilderInterface::ASSIGN_OBJECT_TO_POPULATE]);
|
||||
|
||||
try {
|
||||
$data = $this->serializer->deserialize((string) $request->getContent(), $serializerContext['deserializer_type'] ?? $operation->getClass(), $format, $serializerContext);
|
||||
} catch (PartialDenormalizationException $e) {
|
||||
if (!class_exists(ConstraintViolationList::class)) {
|
||||
throw $e;
|
||||
}
|
||||
|
||||
$violations = new ConstraintViolationList();
|
||||
foreach ($e->getErrors() as $exception) {
|
||||
if (!$exception instanceof NotNormalizableValueException) {
|
||||
continue;
|
||||
}
|
||||
$violations->add($this->createViolationFromException($exception));
|
||||
}
|
||||
if (0 !== \count($violations)) {
|
||||
throw new ValidationException($violations);
|
||||
}
|
||||
} catch (NotNormalizableValueException $e) {
|
||||
// BackedEnum denormalization errors should surface as validation violations (422)
|
||||
// rather than denormalization errors (400). See https://github.com/api-platform/core/issues/8183.
|
||||
if (!class_exists(ConstraintViolationList::class) || !$this->isBackedEnumException($e)) {
|
||||
throw $e;
|
||||
}
|
||||
|
||||
$violations = new ConstraintViolationList();
|
||||
$violations->add($this->createViolationFromException($e));
|
||||
throw new ValidationException($violations);
|
||||
}
|
||||
|
||||
$this->stopwatch?->stop('api_platform.provider.deserialize');
|
||||
|
||||
$request->attributes->set('data', $data);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
private function normalizeExpectedTypes(?array $expectedTypes = null): array
|
||||
{
|
||||
$normalizedTypes = [];
|
||||
|
||||
foreach ($expectedTypes ?? [] as $expectedType) {
|
||||
$normalizedType = $expectedType;
|
||||
|
||||
if (class_exists($expectedType) || interface_exists($expectedType)) {
|
||||
// A backed enum is sent over the wire as its backing scalar (e.g. "string"), not as the
|
||||
// PHP enum class, so report the JSON-visible type rather than the internal FQCN (#8388).
|
||||
if (is_subclass_of($expectedType, \BackedEnum::class) && ($backingType = (new \ReflectionEnum($expectedType))->getBackingType())) {
|
||||
$normalizedType = (string) $backingType;
|
||||
} else {
|
||||
$normalizedType = (new \ReflectionClass($expectedType))->getShortName();
|
||||
}
|
||||
}
|
||||
|
||||
$normalizedTypes[] = $normalizedType;
|
||||
}
|
||||
|
||||
return array_values(array_unique($normalizedTypes));
|
||||
}
|
||||
|
||||
private function createViolationFromException(NotNormalizableValueException $exception): ConstraintViolation
|
||||
{
|
||||
$expectedTypes = $this->normalizeExpectedTypes($exception->getExpectedTypes());
|
||||
$parameters = [];
|
||||
if ($exception->canUseMessageForUser()) {
|
||||
$parameters['hint'] = $exception->getMessage();
|
||||
}
|
||||
|
||||
if (!$expectedTypes && $exception->canUseMessageForUser()) {
|
||||
$violationMessage = $exception->getMessage();
|
||||
|
||||
return new ConstraintViolation($violationMessage, $violationMessage, $parameters, null, $exception->getPath(), null, null, (string) Type::INVALID_TYPE_ERROR);
|
||||
}
|
||||
|
||||
$message = (new Type($expectedTypes))->message;
|
||||
|
||||
return new ConstraintViolation($this->translator->trans($message, ['{{ type }}' => implode('|', $expectedTypes)], 'validators'), $message, $parameters, null, $exception->getPath(), null, null, (string) Type::INVALID_TYPE_ERROR);
|
||||
}
|
||||
|
||||
private function isBackedEnumException(NotNormalizableValueException $exception): bool
|
||||
{
|
||||
foreach ($exception->getExpectedTypes() ?? [] as $expectedType) {
|
||||
if (\is_string($expectedType) && (class_exists($expectedType) || interface_exists($expectedType)) && is_subclass_of($expectedType, \BackedEnum::class)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
for ($previous = $exception->getPrevious(); $previous instanceof \Throwable; $previous = $previous->getPrevious()) {
|
||||
if (!$previous instanceof NotNormalizableValueException) {
|
||||
continue;
|
||||
}
|
||||
foreach ($previous->getExpectedTypes() ?? [] as $expectedType) {
|
||||
if (\is_string($expectedType) && (class_exists($expectedType) || interface_exists($expectedType)) && is_subclass_of($expectedType, \BackedEnum::class)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,368 @@
|
||||
<?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\HttpOperation;
|
||||
use ApiPlatform\Metadata\Patch;
|
||||
use ApiPlatform\Metadata\Post;
|
||||
use ApiPlatform\Metadata\Put;
|
||||
use ApiPlatform\State\Provider\DeserializeProvider;
|
||||
use ApiPlatform\State\ProviderInterface;
|
||||
use ApiPlatform\State\SerializerContextBuilderInterface;
|
||||
use ApiPlatform\Validator\Exception\ValidationException;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
use PHPUnit\Framework\Attributes\IgnoreDeprecations;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpKernel\Exception\UnsupportedMediaTypeHttpException;
|
||||
use Symfony\Component\Serializer\Exception\NotNormalizableValueException;
|
||||
use Symfony\Component\Serializer\Exception\PartialDenormalizationException;
|
||||
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
|
||||
use Symfony\Component\Serializer\SerializerInterface;
|
||||
use Symfony\Component\Validator\Constraints\Type;
|
||||
|
||||
class DeserializeProviderTest extends TestCase
|
||||
{
|
||||
#[IgnoreDeprecations]
|
||||
public function testDeserialize(): void
|
||||
{
|
||||
$this->expectUserDeprecationMessage('Since api-platform/core 4.4: To assign an object to populate you should set "api_assign_object_to_populate" in your denormalizationContext, not defining it is deprecated.');
|
||||
$objectToPopulate = new \stdClass();
|
||||
$serializerContext = [];
|
||||
$operation = new Post(deserialize: true, class: \stdClass::class);
|
||||
$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', \stdClass::class, '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: \stdClass::class);
|
||||
$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: \stdClass::class);
|
||||
$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);
|
||||
}
|
||||
|
||||
#[DataProvider('provideMethodsTriggeringDeprecation')]
|
||||
#[IgnoreDeprecations]
|
||||
public function testDeserializeTriggersDeprecationWhenContextNotSet(HttpOperation $operation): void
|
||||
{
|
||||
$this->expectUserDeprecationMessage('Since api-platform/core 4.4: To assign an object to populate you should set "api_assign_object_to_populate" in your denormalizationContext, not defining it is deprecated.');
|
||||
|
||||
$objectToPopulate = new \stdClass();
|
||||
$serializerContext = [];
|
||||
$decorated = $this->createStub(ProviderInterface::class);
|
||||
$decorated->method('provide')->willReturn($objectToPopulate);
|
||||
|
||||
$serializerContextBuilder = $this->createMock(SerializerContextBuilderInterface::class);
|
||||
$serializerContextBuilder->method('createFromRequest')->willReturn($serializerContext);
|
||||
|
||||
$serializer = $this->createMock(SerializerInterface::class);
|
||||
$serializer->expects($this->once())->method('deserialize')->with(
|
||||
'test',
|
||||
\stdClass::class,
|
||||
'format',
|
||||
['uri_variables' => ['id' => 1], '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 static function provideMethodsTriggeringDeprecation(): iterable
|
||||
{
|
||||
yield 'POST method' => [new Post(deserialize: true, class: \stdClass::class)];
|
||||
yield 'PATCH method' => [new Patch(deserialize: true, class: \stdClass::class)];
|
||||
yield 'PUT method (non-standard)' => [new Put(deserialize: true, class: \stdClass::class, extraProperties: ['standard_put' => false])];
|
||||
}
|
||||
|
||||
public function testDeserializeSetsObjectToPopulateWhenContextIsTrue(): void
|
||||
{
|
||||
$objectToPopulate = new \stdClass();
|
||||
$serializerContext = [SerializerContextBuilderInterface::ASSIGN_OBJECT_TO_POPULATE => true];
|
||||
$operation = new Post(deserialize: true, class: \stdClass::class);
|
||||
$decorated = $this->createStub(ProviderInterface::class);
|
||||
$decorated->method('provide')->willReturn($objectToPopulate);
|
||||
|
||||
$serializerContextBuilder = $this->createMock(SerializerContextBuilderInterface::class);
|
||||
$serializerContextBuilder->method('createFromRequest')->willReturn($serializerContext);
|
||||
|
||||
$serializer = $this->createMock(SerializerInterface::class);
|
||||
$serializer->expects($this->once())->method('deserialize')->with(
|
||||
'test',
|
||||
\stdClass::class,
|
||||
'format',
|
||||
$this->callback(function (array $context) use ($objectToPopulate) {
|
||||
$this->assertArrayHasKey(AbstractNormalizer::OBJECT_TO_POPULATE, $context);
|
||||
$this->assertSame($objectToPopulate, $context[AbstractNormalizer::OBJECT_TO_POPULATE]);
|
||||
|
||||
return true;
|
||||
})
|
||||
)->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]);
|
||||
}
|
||||
|
||||
#[IgnoreDeprecations]
|
||||
public function testDeserializeKeepsTypeMessageWhenExpectedTypesAreSet(): void
|
||||
{
|
||||
$operation = new Post(deserialize: true, class: \stdClass::class);
|
||||
$decorated = $this->createStub(ProviderInterface::class);
|
||||
$decorated->method('provide')->willReturn(null);
|
||||
|
||||
$exception = NotNormalizableValueException::createForUnexpectedDataType(
|
||||
'The data must belong to a backed enumeration of type Suit.',
|
||||
'invalid',
|
||||
['string'],
|
||||
'status',
|
||||
true,
|
||||
);
|
||||
$partialException = new PartialDenormalizationException('Denormalization failed.', [$exception]);
|
||||
|
||||
$serializerContextBuilder = $this->createMock(SerializerContextBuilderInterface::class);
|
||||
$serializerContextBuilder->method('createFromRequest')->willReturn([]);
|
||||
$serializer = $this->createMock(SerializerInterface::class);
|
||||
$serializer->method('deserialize')->willThrowException($partialException);
|
||||
|
||||
$provider = new DeserializeProvider($decorated, $serializer, $serializerContextBuilder);
|
||||
$request = new Request(content: '{"status":"invalid"}');
|
||||
$request->headers->set('CONTENT_TYPE', 'application/json');
|
||||
$request->attributes->set('input_format', 'json');
|
||||
|
||||
try {
|
||||
$provider->provide($operation, [], ['request' => $request]);
|
||||
$this->fail('Expected ValidationException');
|
||||
} catch (ValidationException $e) {
|
||||
$violations = $e->getConstraintViolationList();
|
||||
$this->assertCount(1, $violations);
|
||||
$this->assertSame('This value should be of type string.', $violations[0]->getMessage());
|
||||
$this->assertSame('status', $violations[0]->getPropertyPath());
|
||||
$this->assertSame((string) Type::INVALID_TYPE_ERROR, $violations[0]->getCode());
|
||||
$this->assertSame('The data must belong to a backed enumeration of type Suit.', $violations[0]->getParameters()['hint'] ?? null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Simulates Symfony 8.1 BackedEnumNormalizer behavior (symfony/serializer PR #62574):
|
||||
* when a value has the right type but is not a valid enum case, the exception
|
||||
* is created with expectedTypes=null and a user-friendly message listing valid values.
|
||||
*/
|
||||
#[IgnoreDeprecations]
|
||||
public function testDeserializeUsesExceptionMessageWhenExpectedTypesIsNull(): void
|
||||
{
|
||||
$operation = new Post(deserialize: true, class: \stdClass::class);
|
||||
$decorated = $this->createStub(ProviderInterface::class);
|
||||
$decorated->method('provide')->willReturn(null);
|
||||
|
||||
$ctor = new \ReflectionMethod(NotNormalizableValueException::class, '__construct');
|
||||
if ($ctor->getNumberOfParameters() <= 3) {
|
||||
$this->markTestSkipped('NotNormalizableValueException does not support extended constructor parameters.');
|
||||
}
|
||||
|
||||
$exception = new NotNormalizableValueException(
|
||||
"The data must be one of the following values: 'hearts', 'diamonds', 'clubs', 'spades'",
|
||||
0,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
'suit',
|
||||
true,
|
||||
);
|
||||
$partialException = new PartialDenormalizationException('Denormalization failed.', [$exception]);
|
||||
|
||||
$serializerContextBuilder = $this->createMock(SerializerContextBuilderInterface::class);
|
||||
$serializerContextBuilder->method('createFromRequest')->willReturn([]);
|
||||
$serializer = $this->createMock(SerializerInterface::class);
|
||||
$serializer->method('deserialize')->willThrowException($partialException);
|
||||
|
||||
$provider = new DeserializeProvider($decorated, $serializer, $serializerContextBuilder);
|
||||
$request = new Request(content: '{"suit":"invalid"}');
|
||||
$request->headers->set('CONTENT_TYPE', 'application/json');
|
||||
$request->attributes->set('input_format', 'json');
|
||||
|
||||
try {
|
||||
$provider->provide($operation, [], ['request' => $request]);
|
||||
$this->fail('Expected ValidationException');
|
||||
} catch (ValidationException $e) {
|
||||
$violations = $e->getConstraintViolationList();
|
||||
$this->assertCount(1, $violations);
|
||||
$this->assertSame("The data must be one of the following values: 'hearts', 'diamonds', 'clubs', 'spades'", $violations[0]->getMessage());
|
||||
$this->assertSame("The data must be one of the following values: 'hearts', 'diamonds', 'clubs', 'spades'", $violations[0]->getMessageTemplate());
|
||||
$this->assertSame('suit', $violations[0]->getPropertyPath());
|
||||
$this->assertSame((string) Type::INVALID_TYPE_ERROR, $violations[0]->getCode());
|
||||
}
|
||||
}
|
||||
|
||||
#[IgnoreDeprecations]
|
||||
public function testDeserializeUsesTypeMessageWhenCannotUseMessageForUser(): void
|
||||
{
|
||||
$operation = new Post(deserialize: true, class: \stdClass::class);
|
||||
$decorated = $this->createStub(ProviderInterface::class);
|
||||
$decorated->method('provide')->willReturn(null);
|
||||
|
||||
$exception = NotNormalizableValueException::createForUnexpectedDataType(
|
||||
'Internal error detail',
|
||||
42,
|
||||
['string'],
|
||||
'name',
|
||||
false,
|
||||
);
|
||||
$partialException = new PartialDenormalizationException('Denormalization failed.', [$exception]);
|
||||
|
||||
$serializerContextBuilder = $this->createMock(SerializerContextBuilderInterface::class);
|
||||
$serializerContextBuilder->method('createFromRequest')->willReturn([]);
|
||||
$serializer = $this->createMock(SerializerInterface::class);
|
||||
$serializer->method('deserialize')->willThrowException($partialException);
|
||||
|
||||
$provider = new DeserializeProvider($decorated, $serializer, $serializerContextBuilder);
|
||||
$request = new Request(content: '{"name":42}');
|
||||
$request->headers->set('CONTENT_TYPE', 'application/json');
|
||||
$request->attributes->set('input_format', 'json');
|
||||
|
||||
try {
|
||||
$provider->provide($operation, [], ['request' => $request]);
|
||||
$this->fail('Expected ValidationException');
|
||||
} catch (ValidationException $e) {
|
||||
$violations = $e->getConstraintViolationList();
|
||||
$this->assertCount(1, $violations);
|
||||
$this->assertStringContainsString('string', $violations[0]->getMessage());
|
||||
$this->assertSame('name', $violations[0]->getPropertyPath());
|
||||
$this->assertSame((string) Type::INVALID_TYPE_ERROR, $violations[0]->getCode());
|
||||
$this->assertArrayNotHasKey('hint', $violations[0]->getParameters());
|
||||
}
|
||||
}
|
||||
|
||||
public function testDeserializeDoesNotSetObjectToPopulateWhenContextIsFalse(): void
|
||||
{
|
||||
$objectToPopulate = new \stdClass();
|
||||
$serializerContext = [SerializerContextBuilderInterface::ASSIGN_OBJECT_TO_POPULATE => false];
|
||||
$operation = new Post(deserialize: true, class: \stdClass::class);
|
||||
$decorated = $this->createStub(ProviderInterface::class);
|
||||
$decorated->method('provide')->willReturn($objectToPopulate);
|
||||
|
||||
$serializerContextBuilder = $this->createMock(SerializerContextBuilderInterface::class);
|
||||
$serializerContextBuilder->method('createFromRequest')->willReturn($serializerContext);
|
||||
|
||||
$serializer = $this->createMock(SerializerInterface::class);
|
||||
$serializer->expects($this->once())->method('deserialize')->with(
|
||||
'test',
|
||||
\stdClass::class,
|
||||
'format',
|
||||
$this->callback(function (array $context) {
|
||||
$this->assertArrayNotHasKey(AbstractNormalizer::OBJECT_TO_POPULATE, $context);
|
||||
|
||||
return true;
|
||||
})
|
||||
)->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]);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user