Skip to content

#10: fix bug issue #11

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 2 additions & 12 deletions src/ArgumentResolver/InputArgumentResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,30 +5,20 @@
namespace Sfmok\RequestInput\ArgumentResolver;

use Sfmok\RequestInput\InputInterface;
use Sfmok\RequestInput\Attribute\Input;
use Symfony\Component\HttpFoundation\Request;
use Sfmok\RequestInput\Factory\InputFactoryInterface;
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;
use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface;

class InputArgumentResolver implements ArgumentValueResolverInterface
{
public function __construct(private InputFactoryInterface $inputFactory, private array $inputFormats)
public function __construct(private InputFactoryInterface $inputFactory)
{
}

public function supports(Request $request, ArgumentMetadata $argument): bool
{
if (!is_subclass_of($argument->getType(), InputInterface::class)) {
return false;
}

/** @var Input|null $inputAttribute */
if ($inputAttribute = $request->attributes->get('_input')) {
$this->inputFormats = [$inputAttribute->getFormat()];
}

return \in_array($request->getContentType(), $this->inputFormats);
return is_subclass_of($argument->getType(), InputInterface::class);
}

public function resolve(Request $request, ArgumentMetadata $argument): iterable
Expand Down
2 changes: 1 addition & 1 deletion src/Attribute/Input.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public function __construct(

public function getFormat(): string
{
return $this->format;
return mb_strtolower($this->format);
}

public function getGroups(): array
Expand Down
2 changes: 1 addition & 1 deletion src/DependencyInjection/RequestInputExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ public function load(array $configs, ContainerBuilder $container): void
'$serializer' => new Reference(SerializerInterface::class),
'$validator' => new Reference(ValidatorInterface::class),
'$skipValidation' => $config['skip_validation'],
'$inputFormats' => $config['formats'],
])
->setPublic(false)
;
Expand All @@ -48,7 +49,6 @@ public function load(array $configs, ContainerBuilder $container): void
$container->register(InputArgumentResolver::class)
->setArguments([
'$inputFactory' => new Reference(InputFactoryInterface::class),
'$inputFormats' => $config['formats'],
])
->addTag('controller.argument_value_resolver', ['priority' => 40])
->setPublic(false)
Expand Down
2 changes: 1 addition & 1 deletion src/EventListener/ReadInputListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public function onKernelController(ControllerEvent $event): void
return;
}

if (!\in_array($inputMetadata->getFormat(), Input::INPUT_SUPPORTED_FORMATS)) {
if (!\in_array($inputMetadata->getFormat(), Input::INPUT_SUPPORTED_FORMATS, true)) {
throw new UnexpectedFormatException(sprintf(
'Only the formats [%s] are supported. Got %s.',
implode(', ', Input::INPUT_SUPPORTED_FORMATS),
Expand Down
30 changes: 20 additions & 10 deletions src/Factory/InputFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@
use Sfmok\RequestInput\InputInterface;
use Sfmok\RequestInput\Exception\ValidationException;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Serializer\Exception\NotEncodableValueException;
use Symfony\Component\Serializer\Exception\NotNormalizableValueException;
use Symfony\Component\Serializer\Exception\UnexpectedValueException;
use Symfony\Component\Serializer\SerializerInterface;
use Symfony\Component\Validator\Validator\ValidatorInterface;
Expand All @@ -24,17 +22,21 @@ final class InputFactory implements InputFactoryInterface
public function __construct(
private SerializerInterface $serializer,
private ValidatorInterface $validator,
private bool $skipValidation
private bool $skipValidation,
private array $inputFormats
) {
}

public function createFromRequest(Request $request, string $type, string $format): InputInterface
public function createFromRequest(Request $request, string $type, ?string $format): InputInterface
{
if (!\in_array($format, Input::INPUT_SUPPORTED_FORMATS)) {
$inputMetadata = $request->attributes->get('_input');
$supportedFormats = (array) ($inputMetadata?->getFormat() ?? $this->inputFormats);

if (!\in_array($format, $supportedFormats, true)) {
throw new UnexpectedFormatException(sprintf(
'Only the formats [%s] are supported. Got %s.',
implode(', ', Input::INPUT_SUPPORTED_FORMATS),
$format
'Unexpected request content type, expected any of [%s]. Got "%s".',
implode(', ', $this->getExpectedContentTypes($request, $supportedFormats)),
$request->getMimeType($format ?? '')
));
}

Expand All @@ -44,8 +46,6 @@ public function createFromRequest(Request $request, string $type, string $format
$format = Input::INPUT_JSON_FORMAT;
}

$inputMetadata = $request->attributes->get('_input');

try {
$input = $this->serializer->deserialize($data, $type, $format, $inputMetadata?->getContext() ?? []);
} catch (UnexpectedValueException $exception) {
Expand All @@ -63,4 +63,14 @@ public function createFromRequest(Request $request, string $type, string $format

return $input;
}

private function getExpectedContentTypes(Request $request, array $expectedFormats): array
{
$expectedContentTypes = [];
foreach ($expectedFormats as $format) {
$expectedContentTypes = [...$expectedContentTypes, ...$request->getMimeTypes($format)];
}

return $expectedContentTypes;
}
}
2 changes: 1 addition & 1 deletion src/Factory/InputFactoryInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@

interface InputFactoryInterface
{
public function createFromRequest(Request $request, string $type, string $format): InputInterface;
public function createFromRequest(Request $request, string $type, ?string $format): InputInterface;
}
109 changes: 15 additions & 94 deletions tests/ArgumentResolver/InputArgumentResolverTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
use Prophecy\PhpUnit\ProphecyTrait;
use Prophecy\Prophecy\ObjectProphecy;
use Sfmok\RequestInput\ArgumentResolver\InputArgumentResolver;
use Sfmok\RequestInput\Attribute\Input;
use Sfmok\RequestInput\Factory\InputFactoryInterface;
use Sfmok\RequestInput\Tests\Fixtures\Input\DummyInput;
use Symfony\Component\HttpFoundation\Request;
Expand All @@ -25,119 +24,41 @@ protected function setUp(): void
$this->inputFactory = $this->prophesize(InputFactoryInterface::class);
}

public function testSupportsWithArgumentTypeNotInput(): void
{
$request = new Request();
$request->headers->set('Content-Type', 'application/json');
$argument = new ArgumentMetadata('foo', \stdClass::class, false, false, null);

$resolver = $this->createArgumentResolver(Input::INPUT_SUPPORTED_FORMATS);
$this->assertFalse($resolver->supports($request, $argument));
}

/**
* @dataProvider provideSupportsWithDefaultGlobalFormats
*/
public function testSupportsWithDefaultGlobalFormats(bool $expected, ?string $contentType): void
{
$request = new Request();
$request->headers->set('Content-Type', $contentType);
$argument = new ArgumentMetadata('foo', DummyInput::class, false, false, null);

$resolver = $this->createArgumentResolver(Input::INPUT_SUPPORTED_FORMATS);
$this->assertSame($expected, $resolver->supports($request, $argument));
}

/**
* @dataProvider provideSupportsWithCustomGlobalFormats
*/
public function testSupportsWithCustomGlobalFormats(bool $expected, ?string $contentType): void
{
$request = new Request();
$request->headers->set('Content-Type', $contentType);

$argument = new ArgumentMetadata('foo', DummyInput::class, false, false, null);

$resolver = $this->createArgumentResolver(['json']);
$this->assertSame($expected, $resolver->supports($request, $argument));
}

/**
* @dataProvider provideSupportsWithCustomFormatsInInputAttribute
* @dataProvider provideSupportData
*/
public function testSupportsWithCustomFormatsInInputAttribute(bool $expected, ?string $contentType): void
public function testSupports(mixed $type, bool $expectedResult): void
{
$request = new Request();
$request->headers->set('Content-Type', $contentType);
$request->attributes->set('_input', new Input('xml'));

$argument = new ArgumentMetadata('foo', DummyInput::class, false, false, null);

$resolver = $this->createArgumentResolver(Input::INPUT_SUPPORTED_FORMATS);
$this->assertSame($expected, $resolver->supports($request, $argument));
$argument = new ArgumentMetadata('foo', $type, false, false, null);
$this->assertSame($expectedResult, $this->createArgumentResolver()->supports(new Request(), $argument));
}

public function testResolveSucceeds(): void
public function testResolve(): void
{
$dummyInput = new DummyInput();

$request = new Request();
$request->headers->set('Content-Type', 'application/json');

$argument = new ArgumentMetadata('foo', DummyInput::class, false, false, null);

$resolver = $this->createArgumentResolver([Input::INPUT_SUPPORTED_FORMATS]);
$argument = new ArgumentMetadata('foo', $dummyInput::class, false, false, null);

$this->inputFactory
->createFromRequest($request, $argument->getType(), $request->getContentType())
->createFromRequest($request, $dummyInput::class, $request->getContentType())
->shouldBeCalledOnce()
->willReturn($dummyInput)
;

$resolver = $this->createArgumentResolver();
$this->assertEquals([$dummyInput], iterator_to_array($resolver->resolve($request, $argument)));
}

public function provideSupportsWithDefaultGlobalFormats(): iterable
{
yield [false, null];
yield [false, 'application/rdf+xml'];
yield [false, 'text/html'];
yield [false, 'application/javascript'];
yield [false, 'text/plain'];
yield [false, 'application/ld+json'];
yield [true, 'application/json'];
yield [true, 'application/xml'];
yield [true, 'multipart/form-data'];
}

public function provideSupportsWithCustomGlobalFormats(): iterable
{
yield [false, null];
yield [false, 'application/rdf+xml'];
yield [false, 'text/html'];
yield [false, 'application/javascript'];
yield [false, 'text/plain'];
yield [false, 'application/ld+json'];
yield [true, 'application/json'];
yield [false, 'application/xml'];
yield [false, 'multipart/form-data'];
}

public function provideSupportsWithCustomFormatsInInputAttribute(): iterable
public function provideSupportData(): iterable
{
yield [false, null];
yield [false, 'application/rdf+xml'];
yield [false, 'text/html'];
yield [false, 'application/javascript'];
yield [false, 'text/plain'];
yield [false, 'application/ld+json'];
yield [false, 'application/json'];
yield [true, 'application/xml'];
yield [false, 'multipart/form-data'];
yield [null, false];
yield [\stdClass::class, false];
yield ["Foo", false];
yield [DummyInput::class, true];
}

private function createArgumentResolver(array $formats): InputArgumentResolver
private function createArgumentResolver(): InputArgumentResolver
{
return new InputArgumentResolver($this->inputFactory->reveal(), $formats);
return new InputArgumentResolver($this->inputFactory->reveal());
}
}
17 changes: 17 additions & 0 deletions tests/DependencyInjection/RequestInputExtensionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,23 @@ public function testLoadConfiguration(): void
$this->assertServiceHasTags(ReadInputListener::class, ['kernel.event_listener']);
}

public function testLoadConfigurationWithDisabledOption(): void
{
(new RequestInputExtension())->load(['request_input' => ['enabled' => false]], $this->container);

$services = [
InputArgumentResolver::class,
ExceptionListener::class,
ReadInputListener::class,
InputFactory::class,
InputMetadataFactory::class
];

foreach ($services as $service) {
$this->assertFalse($this->container->hasDefinition($service), sprintf('Definition "%s" is found.', $service));
}
}

private function assertContainerHas(array $services, array $aliases = []): void
{
foreach ($services as $service) {
Expand Down
Loading