Skip to content
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

#10: fix bug issue #11

Closed
Closed
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
@@ -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
2 changes: 1 addition & 1 deletion src/Attribute/Input.php
Original file line number Diff line number Diff line change
@@ -21,7 +21,7 @@ public function __construct(

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

public function getGroups(): array
2 changes: 1 addition & 1 deletion src/DependencyInjection/RequestInputExtension.php
Original file line number Diff line number Diff line change
@@ -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)
;
@@ -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)
2 changes: 1 addition & 1 deletion src/EventListener/ReadInputListener.php
Original file line number Diff line number Diff line change
@@ -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),
30 changes: 20 additions & 10 deletions src/Factory/InputFactory.php
Original file line number Diff line number Diff line change
@@ -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;
@@ -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 ?? '')
));
}

@@ -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) {
@@ -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
@@ -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
@@ -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;
@@ -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
@@ -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) {
Loading