From de6e3f546a26c5ad5444d8e2448d81faec36bd73 Mon Sep 17 00:00:00 2001 From: Antoine Bluchet Date: Tue, 17 Sep 2024 10:26:36 +0200 Subject: [PATCH] fix(laravel): validate enum schema within filter (#6615) --- ...dationResourceMetadataCollectionFactory.php | 6 +++--- .../State/ParameterValidatorProvider.php | 18 ++++++++++++------ src/Laravel/Tests/EloquentTest.php | 8 +++++++- src/Metadata/Parameter.php | 15 ++++----------- 4 files changed, 26 insertions(+), 21 deletions(-) diff --git a/src/Laravel/Metadata/ParameterValidationResourceMetadataCollectionFactory.php b/src/Laravel/Metadata/ParameterValidationResourceMetadataCollectionFactory.php index 5c0f214284..a92fd20535 100644 --- a/src/Laravel/Metadata/ParameterValidationResourceMetadataCollectionFactory.php +++ b/src/Laravel/Metadata/ParameterValidationResourceMetadataCollectionFactory.php @@ -136,9 +136,9 @@ private function addSchemaValidation(Parameter $parameter): Parameter $assertions[] = 'multiple_of:'.$schema['multipleOf']; } - // if (isset($schema['enum'])) { - // $assertions[] = [Rule::enum($schema['enum'])]; - // } + if (isset($schema['enum'])) { + $assertions[] = Rule::in($schema['enum']); + } if (isset($schema['type']) && 'array' === $schema['type']) { $assertions[] = 'array'; diff --git a/src/Laravel/State/ParameterValidatorProvider.php b/src/Laravel/State/ParameterValidatorProvider.php index 8e57a07733..50560acd8f 100644 --- a/src/Laravel/State/ParameterValidatorProvider.php +++ b/src/Laravel/State/ParameterValidatorProvider.php @@ -13,6 +13,7 @@ namespace ApiPlatform\Laravel\State; +use ApiPlatform\Metadata\Exception\RuntimeException; use ApiPlatform\Metadata\Operation; use ApiPlatform\State\ParameterNotFound; use ApiPlatform\State\ProviderInterface; @@ -59,18 +60,23 @@ public function provide(Operation $operation, array $uriVariables = [], array $c } $key = $parameter->getKey(); + if (null === $key) { + throw new RuntimeException('A parameter must have a defined key.'); + } + $value = $parameter->getValue(); if ($value instanceof ParameterNotFound) { $value = null; } - foreach ((array) $constraints as $k => $c) { - if (!\is_string($k)) { - $k = $key; - } - - $allConstraints[$k] = $c; + // Basically renames our key from order[:property] to order.* to assign the rule properly (see https://laravel.com/docs/11.x/validation#rule-in) + if (str_contains($key, '[:property]')) { + $k = str_replace('[:property]', '', $key); + $allConstraints[$k.'.*'] = $constraints; + continue; } + + $allConstraints[$key] = $constraints; } $validator = Validator::make($request->query->all(), $allConstraints); diff --git a/src/Laravel/Tests/EloquentTest.php b/src/Laravel/Tests/EloquentTest.php index 757fe083e6..c9bb3b35b8 100644 --- a/src/Laravel/Tests/EloquentTest.php +++ b/src/Laravel/Tests/EloquentTest.php @@ -235,7 +235,7 @@ public function testSearchFilterWithPropertyPlaceholder(): void public function testOrderFilterWithPropertyPlaceholder(): void { - $res = $this->get('/api/authors?order[id]=desc', ['Accept' => ['application/ld+json']]); + $res = $this->get('/api/authors?order[id]=desc', ['Accept' => ['application/ld+json']])->json(); $this->assertSame($res['member'][0]['id'], 10); } @@ -362,4 +362,10 @@ public function testRangeGreaterThanEqualFilter(): void $this->assertSame($response->json()['member'][1]['@id'], $bookAfter['@id']); $this->assertSame($response->json()['totalItems'], 2); } + + public function testWrongOrderFilter(): void + { + $res = $this->get('/api/authors?order[name]=something', ['Accept' => ['application/ld+json']]); + $this->assertEquals($res->getStatusCode(), 422); + } } diff --git a/src/Metadata/Parameter.php b/src/Metadata/Parameter.php index 73a1c8cae3..aa91e5d762 100644 --- a/src/Metadata/Parameter.php +++ b/src/Metadata/Parameter.php @@ -17,7 +17,6 @@ use ApiPlatform\OpenApi\Model\Parameter as OpenApiParameter; use ApiPlatform\State\ParameterNotFound; use ApiPlatform\State\ParameterProviderInterface; -use Symfony\Component\Validator\Constraint; /** * @experimental @@ -29,7 +28,7 @@ abstract class Parameter * @param array $extraProperties * @param ParameterProviderInterface|callable|string|null $provider * @param FilterInterface|string|null $filter - * @param Constraint|array|string|Constraint[]|null $constraints + * @param mixed $constraints an array of Symfony constraints, or an array of Laravel rules */ public function __construct( protected ?string $key = null, @@ -42,7 +41,7 @@ public function __construct( protected ?bool $required = null, protected ?int $priority = null, protected ?false $hydra = null, - protected Constraint|array|string|null $constraints = null, + protected mixed $constraints = null, protected string|\Stringable|null $security = null, protected ?string $securityMessage = null, protected ?array $extraProperties = [], @@ -106,10 +105,7 @@ public function getHydra(): ?bool return $this->hydra; } - /** - * @return Constraint|string|array|Constraint[]|null - */ - public function getConstraints(): Constraint|string|array|null + public function getConstraints(): mixed { return $this->constraints; } @@ -239,10 +235,7 @@ public function withHydra(false $hydra): static return $self; } - /** - * @param string|array|Constraint[]|Constraint $constraints - */ - public function withConstraints(string|array|Constraint $constraints): static + public function withConstraints(mixed $constraints): static { $self = clone $this; $self->constraints = $constraints;