Skip to content

Commit

Permalink
feat(validator): parameter validation
Browse files Browse the repository at this point in the history
  • Loading branch information
soyuka committed Apr 5, 2024
1 parent ba1c61f commit 22782dd
Show file tree
Hide file tree
Showing 6 changed files with 113 additions and 6 deletions.
27 changes: 23 additions & 4 deletions src/Metadata/Parameter.php
Expand Up @@ -15,17 +15,19 @@

use ApiPlatform\OpenApi;
use ApiPlatform\State\ProviderInterface;
use Symfony\Component\Validator\Constraint;

/**
* @experimental
*/
abstract class Parameter
{
/**
* @param array{type?: string}|null $schema
* @param array<string, mixed> $extraProperties
* @param ProviderInterface|callable|string|null $provider
* @param FilterInterface|string|null $filter
* @param array{type?: string}|null $schema
* @param array<string, mixed> $extraProperties
* @param ProviderInterface|callable|string|null $provider
* @param FilterInterface|string|null $filter
* @param Symfony\Component\Validator\Constraint|string|null $constraint
*/
public function __construct(

Check failure on line 32 in src/Metadata/Parameter.php

View workflow job for this annotation

GitHub Actions / PHPStan (PHP 8.3)

PHPDoc tag @param for parameter $constraint with type ApiPlatform\Metadata\Symfony\Component\Validator\Constraint|string|null is not subtype of native type array|Symfony\Component\Validator\Constraint|null.
protected ?string $key = null,
Expand All @@ -37,6 +39,7 @@ public function __construct(
protected ?string $description = null,
protected ?bool $required = null,
protected ?int $priority = null,
protected array|Constraint|null $constraint = null,

Check failure on line 42 in src/Metadata/Parameter.php

View workflow job for this annotation

GitHub Actions / PHPStan (PHP 8.3)

PHPDoc type for property ApiPlatform\Metadata\Parameter::$constraint with type ApiPlatform\Metadata\Symfony\Component\Validator\Constraint|string|null is not subtype of native type array|Symfony\Component\Validator\Constraint|null.

Check failure on line 42 in src/Metadata/Parameter.php

View workflow job for this annotation

GitHub Actions / PHPStan (PHP 8.3)

Parameter $constraint of method ApiPlatform\Metadata\Parameter::__construct() has invalid type ApiPlatform\Metadata\Symfony\Component\Validator\Constraint.

Check failure on line 42 in src/Metadata/Parameter.php

View workflow job for this annotation

GitHub Actions / PHPStan (PHP 8.3)

Property ApiPlatform\Metadata\Parameter::$constraint has unknown class ApiPlatform\Metadata\Symfony\Component\Validator\Constraint as its type.
protected ?array $extraProperties = [],
) {
}
Expand Down Expand Up @@ -89,6 +92,14 @@ public function getPriority(): ?int
return $this->priority;
}

/**
* @return Constraint|Constraint[]|null
*/
public function getConstraint(): array|Constraint|null
{
return $this->constraint;
}

/**
* @return array<string, mixed>
*/
Expand Down Expand Up @@ -178,6 +189,14 @@ public function withRequired(bool $required): static
return $self;
}

public function withConstraint(array|Constraint $constraint): static
{
$self = clone $this;
$self->constraint = $constraint;

return $self;
}

/**
* @param array<string, mixed> $extraProperties
*/
Expand Down
1 change: 1 addition & 0 deletions src/Metadata/Tests/Fixtures/ApiResource/WithParameter.php
Expand Up @@ -17,6 +17,7 @@
use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\HeaderParameter;
use ApiPlatform\Metadata\QueryParameter;
use Symfony\Component\Validator\Constraints\Required;

#[ApiResource(
parameters: [
Expand Down
Expand Up @@ -818,6 +818,7 @@ private function registerValidatorConfiguration(ContainerBuilder $container, arr
if (interface_exists(ValidatorInterface::class)) {
$loader->load('metadata/validator.xml');
$loader->load('validator/validator.xml');
$loader->load('symfony/parameter_validator.xml');

if ($this->isConfigEnabled($container, $config['graphql'])) {
$loader->load('graphql/validator.xml');
Expand Down
@@ -0,0 +1,11 @@
<?xml version="1.0" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<service id="api_platform.symfony.parameter_validator" class="ApiPlatform\Symfony\Validator\State\ParameterValidatorProvider" public="true" decorates="api_platform.state_provider.parameter">
<argument type="service" id="api_platform.symfony.parameter_validator.inner" />
<argument type="service" id="validator" />
</service>
</services>
</container>
74 changes: 74 additions & 0 deletions src/Symfony/Validator/State/ParameterValidatorProvider.php
@@ -0,0 +1,74 @@
<?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\Validator\State;

use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProviderInterface;
use ApiPlatform\Validator\Exception\ValidationException;
use Symfony\Component\Validator\ConstraintViolation;
use Symfony\Component\Validator\ConstraintViolationList;
use Symfony\Component\Validator\Validator\ValidatorInterface;

final class ParameterValidatorProvider implements ProviderInterface
{
public function __construct(
private readonly ProviderInterface $decorated,
private readonly ValidatorInterface $validator
) {
}

public function provide(Operation $operation, array $uriVariables = [], array $context = []): object|array|null
{
$body = $this->decorated->provide($operation, $uriVariables, $context);
if (!$context['request'] ?? null) {

Check failure on line 34 in src/Symfony/Validator/State/ParameterValidatorProvider.php

View workflow job for this annotation

GitHub Actions / PHPStan (PHP 8.3)

Expression on left side of ?? is not nullable.
return $body;
}

$operation = $context['request']->attributes->get('_api_operation');
foreach ($operation->getParameters() as $parameter) {
if (!$constraints = $parameter->getConstraint()) {
continue;
}

// This is computed in @see ApiPlatform\State\Provider\ParameterProvider
if (!\array_key_exists('_api_values', $parameter->getExtraProperties())) {
continue;
}

$violations = $this->validator->validate(current($parameter->getExtraProperties()['_api_values']), $constraints);

if (0 !== \count($violations)) {
$constraintViolationList = new ConstraintViolationList();
foreach ($violations as $violation) {
$constraintViolationList->add(new ConstraintViolation(
$violation->getMessage(),
$violation->getMessageTemplate(),
$violation->getParameters(),
$violation->getRoot(),
$parameter->getKey(),
$violation->getInvalidValue(),
$violation->getPlural(),
$violation->getCode(),
$violation->getConstraint(),
$violation->getCause()
));
}

throw new ValidationException($constraintViolationList);
}
}

return $body;
}
}
5 changes: 3 additions & 2 deletions tests/Fixtures/TestBundle/ApiResource/WithParameter.php
Expand Up @@ -23,6 +23,7 @@
use ApiPlatform\Tests\Fixtures\TestBundle\Parameter\CustomGroupParameterProvider;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\Serializer\Attribute\Groups;
use Symfony\Component\Validator\Constraints\NotBlank;

#[Get(
uriTemplate: 'with_parameters/{id}{._format}',
Expand All @@ -42,9 +43,9 @@
provider: [self::class, 'provide']
)]
#[GetCollection(
uriTemplate: 'with_parameters_collection',
uriTemplate: 'with_parameters_collection{._format}',
parameters: [
'hydra' => new QueryParameter(property: 'a', required: true),
'hydra' => new QueryParameter(property: 'a', required: true, constraint: new NotBlank()),
],
provider: [self::class, 'collectionProvider']
)]
Expand Down

0 comments on commit 22782dd

Please sign in to comment.