diff --git a/src/Validator/Util/ParameterValidationConstraints.php b/src/Validator/Util/ParameterValidationConstraints.php index 0396c24920..3a7d06583c 100644 --- a/src/Validator/Util/ParameterValidationConstraints.php +++ b/src/Validator/Util/ParameterValidationConstraints.php @@ -34,7 +34,9 @@ use Symfony\Component\Validator\Constraints\Regex; use Symfony\Component\Validator\Constraints\Sequentially; use Symfony\Component\Validator\Constraints\Type; +use Symfony\Component\Validator\Constraints\Ulid; use Symfony\Component\Validator\Constraints\Unique; +use Symfony\Component\Validator\Constraints\Uuid; /** * Helper to get a set of validation constraints for a given Parameter. @@ -148,6 +150,37 @@ public static function getParameterValidationConstraints(Parameter $parameter, ? } } + if (isset($schema['type'], $schema['format']) && 'string' === $schema['type'] && 'uuid' === $schema['format']) { + $assertions[] = new Uuid(); + } + + if (isset($schema['type'], $schema['format']) && 'string' === $schema['type'] && 'ulid' === $schema['format']) { + $assertions[] = new Ulid(); + } + + if (isset($schema['oneOf']) && 2 === \count($schema['oneOf'])) { + $oneOfIndexByType = array_column($schema['oneOf'], null, 'type'); + if ( + 'uuid' === ($oneOfIndexByType['string']['format'] ?? '') + && 'uuid' === ($oneOfIndexByType['array']['items']['format'] ?? '') + ) { + $assertions[] = new AtLeastOneOf([ + new Uuid(), + new All([new Uuid()]), + ]); + } + + if ( + 'ulid' === ($oneOfIndexByType['string']['format'] ?? '') + && 'ulid' === ($oneOfIndexByType['array']['items']['format'] ?? '') + ) { + $assertions[] = new AtLeastOneOf([ + new Ulid(), + new All([new Ulid()]), + ]); + } + } + return $assertions; } } diff --git a/tests/Fixtures/TestBundle/ApiResource/WithParameter.php b/tests/Fixtures/TestBundle/ApiResource/WithParameter.php index 7f5efb457e..9c4f3c0130 100644 --- a/tests/Fixtures/TestBundle/ApiResource/WithParameter.php +++ b/tests/Fixtures/TestBundle/ApiResource/WithParameter.php @@ -211,6 +211,30 @@ ], provider: [self::class, 'noopProvider'] )] +#[GetCollection( + uriTemplate: 'header_uuid', + parameters: [ + 'uuid' => new HeaderParameter( + schema: [ + 'type' => 'string', + 'format' => 'uuid', + ], + ), + ], + provider: [self::class, 'noopProvider'] +)] +#[GetCollection( + uriTemplate: 'header_ulid', + parameters: [ + 'ulid' => new HeaderParameter( + schema: [ + 'type' => 'string', + 'format' => 'ulid', + ], + ), + ], + provider: [self::class, 'noopProvider'] +)] #[GetCollection( uriTemplate: 'query_integer', parameters: [ @@ -254,6 +278,30 @@ ], provider: [self::class, 'noopProvider'] )] +#[GetCollection( + uriTemplate: 'query_uuid', + parameters: [ + 'uuid' => new QueryParameter( + schema: [ + 'type' => 'string', + 'format' => 'uuid', + ], + ), + ], + provider: [self::class, 'noopProvider'] +)] +#[GetCollection( + uriTemplate: 'query_ulid', + parameters: [ + 'ulid' => new QueryParameter( + schema: [ + 'type' => 'string', + 'format' => 'ulid', + ], + ), + ], + provider: [self::class, 'noopProvider'] +)] #[Get( uriTemplate: 'with_parameters_iris', parameters: [ diff --git a/tests/Functional/Parameters/ParameterTest.php b/tests/Functional/Parameters/ParameterTest.php index 2290cb475e..0573150481 100644 --- a/tests/Functional/Parameters/ParameterTest.php +++ b/tests/Functional/Parameters/ParameterTest.php @@ -154,6 +154,14 @@ public static function provideHeaderValues(): iterable yield 'valid boolean 1 header_boolean' => ['header_boolean', ['Lorem' => 1], 200]; yield 'valid boolean 1 string header_boolean' => ['header_boolean', ['Lorem' => '1'], 200]; yield 'invalid boolean header_boolean' => ['header_boolean', ['Lorem' => 'string'], 422]; + + // query_uuid + yield 'valid uuid header_uuid' => ['header_uuid', ['uuid' => '216fff40-98d9-11e3-a5e2-0800200c9a66'], 200]; + yield 'invalid uuid header_uuid' => ['header_uuid', ['uuid' => 'invalid_uuid'], 422]; + + // query_ulid + yield 'valid ulid header_ulid' => ['header_ulid', ['ulid' => '01ARZ3NDEKTSV4RRFFQ69G5FAV'], 200]; + yield 'invalid ulid header_ulid' => ['header_ulid', ['ulid' => 'invalid_uuid'], 422]; } #[DataProvider('provideQueryValues')] @@ -188,6 +196,14 @@ public static function provideQueryValues(): iterable yield 'valid boolean 1 query_boolean' => ['query_boolean', ['Lorem' => 1], 200]; yield 'valid boolean 1 string query_boolean' => ['query_boolean', ['Lorem' => '1'], 200]; yield 'invalid boolean query_boolean' => ['query_boolean', ['Lorem' => 'string'], 422]; + + // query_uuid + yield 'valid uuid query_uuid' => ['query_uuid', ['uuid' => '216fff40-98d9-11e3-a5e2-0800200c9a66'], 200]; + yield 'invalid uuid query_uuid' => ['query_uuid', ['uuid' => 'invalid_uuid'], 422]; + + // query_ulid + yield 'valid ulid query_ulid' => ['query_ulid', ['ulid' => '01ARZ3NDEKTSV4RRFFQ69G5FAV'], 200]; + yield 'invalid ulid query_ulid' => ['query_ulid', ['ulid' => 'invalid_uuid'], 422]; } #[DataProvider('provideCountryValues')] diff --git a/tests/Functional/Uuid/UuidFilterBaseTestCase.php b/tests/Functional/Uuid/UuidFilterBaseTestCase.php index f319ee97c9..a5061a5334 100644 --- a/tests/Functional/Uuid/UuidFilterBaseTestCase.php +++ b/tests/Functional/Uuid/UuidFilterBaseTestCase.php @@ -142,13 +142,13 @@ public function testSearchFilterByInvalidUuid(): void $manager->persist($this->createDevice()); $manager->flush(); - $response = self::createClient()->request('GET', '/'.$this->getUrlPrefix().'_devices', [ + self::createClient()->request('GET', '/'.$this->getUrlPrefix().'_devices', [ 'query' => [ 'id' => 'invalid-uuid', ], ]); - $this->assertResponseStatusCodeSame(Response::HTTP_BAD_REQUEST); + self::assertResponseStatusCodeSame(Response::HTTP_UNPROCESSABLE_ENTITY); } public function testSearchFilterByManyInvalidUuid(): void @@ -160,13 +160,13 @@ public function testSearchFilterByManyInvalidUuid(): void $manager->persist($this->createDevice()); $manager->flush(); - $response = self::createClient()->request('GET', '/'.$this->getUrlPrefix().'_devices', [ + self::createClient()->request('GET', '/'.$this->getUrlPrefix().'_devices', [ 'query' => [ 'id' => ['invalid-uuid', 'other-invalid-uuid'], ], ]); - $this->assertResponseStatusCodeSame(Response::HTTP_BAD_REQUEST); + self::assertResponseStatusCodeSame(Response::HTTP_UNPROCESSABLE_ENTITY); } public function testSearchFilterOnManyToOneRelationByUuid(): void @@ -262,7 +262,7 @@ public function testSearchFilterOnManyToOneRelationByInvalidUuids(): void $manager->persist($this->createDeviceEndpoint(null, $bazDevice)); $manager->flush(); - $response = self::createClient()->request('GET', '/'.$this->getUrlPrefix().'_device_endpoints', [ + self::createClient()->request('GET', '/'.$this->getUrlPrefix().'_device_endpoints', [ 'query' => [ 'myDevice' => [ 'invalid-uuid', @@ -271,7 +271,7 @@ public function testSearchFilterOnManyToOneRelationByInvalidUuids(): void ], ]); - $this->assertResponseStatusCodeSame(Response::HTTP_BAD_REQUEST); + self::assertResponseStatusCodeSame(Response::HTTP_UNPROCESSABLE_ENTITY); } public function testGetOpenApiDescription(): void