Skip to content

Commit 5469d7f

Browse files
committed
fix(validator): uuid/ulid parameter validation
1 parent 73402fc commit 5469d7f

File tree

4 files changed

+103
-6
lines changed

4 files changed

+103
-6
lines changed

src/Validator/Util/ParameterValidationConstraints.php

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,9 @@
3434
use Symfony\Component\Validator\Constraints\Regex;
3535
use Symfony\Component\Validator\Constraints\Sequentially;
3636
use Symfony\Component\Validator\Constraints\Type;
37+
use Symfony\Component\Validator\Constraints\Ulid;
3738
use Symfony\Component\Validator\Constraints\Unique;
39+
use Symfony\Component\Validator\Constraints\Uuid;
3840

3941
/**
4042
* Helper to get a set of validation constraints for a given Parameter.
@@ -148,6 +150,37 @@ public static function getParameterValidationConstraints(Parameter $parameter, ?
148150
}
149151
}
150152

153+
if (isset($schema['type'], $schema['format']) && 'string' === $schema['type'] && 'uuid' === $schema['format']) {
154+
$assertions[] = new Uuid();
155+
}
156+
157+
if (isset($schema['type'], $schema['format']) && 'string' === $schema['type'] && 'ulid' === $schema['format']) {
158+
$assertions[] = new Ulid();
159+
}
160+
161+
if (isset($schema['oneOf']) && 2 === \count($schema['oneOf'])) {
162+
$oneOfIndexByType = array_column($schema['oneOf'], null, 'type');
163+
if (
164+
'uuid' === ($oneOfIndexByType['string']['format'] ?? '')
165+
&& 'uuid' === ($oneOfIndexByType['array']['items']['format'] ?? '')
166+
) {
167+
$assertions[] = new AtLeastOneOf([
168+
new Uuid(),
169+
new All([new Uuid()]),
170+
]);
171+
}
172+
173+
if (
174+
'ulid' === ($oneOfIndexByType['string']['format'] ?? '')
175+
&& 'ulid' === ($oneOfIndexByType['array']['items']['format'] ?? '')
176+
) {
177+
$assertions[] = new AtLeastOneOf([
178+
new Ulid(),
179+
new All([new Ulid()]),
180+
]);
181+
}
182+
}
183+
151184
return $assertions;
152185
}
153186
}

tests/Fixtures/TestBundle/ApiResource/WithParameter.php

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,30 @@
211211
],
212212
provider: [self::class, 'noopProvider']
213213
)]
214+
#[GetCollection(
215+
uriTemplate: 'header_uuid',
216+
parameters: [
217+
'uuid' => new HeaderParameter(
218+
schema: [
219+
'type' => 'string',
220+
'format' => 'uuid',
221+
],
222+
),
223+
],
224+
provider: [self::class, 'noopProvider']
225+
)]
226+
#[GetCollection(
227+
uriTemplate: 'header_ulid',
228+
parameters: [
229+
'ulid' => new HeaderParameter(
230+
schema: [
231+
'type' => 'string',
232+
'format' => 'ulid',
233+
],
234+
),
235+
],
236+
provider: [self::class, 'noopProvider']
237+
)]
214238
#[GetCollection(
215239
uriTemplate: 'query_integer',
216240
parameters: [
@@ -254,6 +278,30 @@
254278
],
255279
provider: [self::class, 'noopProvider']
256280
)]
281+
#[GetCollection(
282+
uriTemplate: 'query_uuid',
283+
parameters: [
284+
'uuid' => new QueryParameter(
285+
schema: [
286+
'type' => 'string',
287+
'format' => 'uuid',
288+
],
289+
),
290+
],
291+
provider: [self::class, 'noopProvider']
292+
)]
293+
#[GetCollection(
294+
uriTemplate: 'query_ulid',
295+
parameters: [
296+
'ulid' => new QueryParameter(
297+
schema: [
298+
'type' => 'string',
299+
'format' => 'ulid',
300+
],
301+
),
302+
],
303+
provider: [self::class, 'noopProvider']
304+
)]
257305
#[Get(
258306
uriTemplate: 'with_parameters_iris',
259307
parameters: [

tests/Functional/Parameters/ParameterTest.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,14 @@ public static function provideHeaderValues(): iterable
154154
yield 'valid boolean 1 header_boolean' => ['header_boolean', ['Lorem' => 1], 200];
155155
yield 'valid boolean 1 string header_boolean' => ['header_boolean', ['Lorem' => '1'], 200];
156156
yield 'invalid boolean header_boolean' => ['header_boolean', ['Lorem' => 'string'], 422];
157+
158+
// query_uuid
159+
yield 'valid uuid header_uuid' => ['header_uuid', ['uuid' => '216fff40-98d9-11e3-a5e2-0800200c9a66'], 200];
160+
yield 'invalid uuid header_uuid' => ['header_uuid', ['uuid' => 'invalid_uuid'], 422];
161+
162+
// query_ulid
163+
yield 'valid ulid header_ulid' => ['header_ulid', ['ulid' => '01ARZ3NDEKTSV4RRFFQ69G5FAV'], 200];
164+
yield 'invalid ulid header_ulid' => ['header_ulid', ['ulid' => 'invalid_uuid'], 422];
157165
}
158166

159167
#[DataProvider('provideQueryValues')]
@@ -188,6 +196,14 @@ public static function provideQueryValues(): iterable
188196
yield 'valid boolean 1 query_boolean' => ['query_boolean', ['Lorem' => 1], 200];
189197
yield 'valid boolean 1 string query_boolean' => ['query_boolean', ['Lorem' => '1'], 200];
190198
yield 'invalid boolean query_boolean' => ['query_boolean', ['Lorem' => 'string'], 422];
199+
200+
// query_uuid
201+
yield 'valid uuid query_uuid' => ['query_uuid', ['uuid' => '216fff40-98d9-11e3-a5e2-0800200c9a66'], 200];
202+
yield 'invalid uuid query_uuid' => ['query_uuid', ['uuid' => 'invalid_uuid'], 422];
203+
204+
// query_ulid
205+
yield 'valid ulid query_ulid' => ['query_ulid', ['ulid' => '01ARZ3NDEKTSV4RRFFQ69G5FAV'], 200];
206+
yield 'invalid ulid query_ulid' => ['query_ulid', ['ulid' => 'invalid_uuid'], 422];
191207
}
192208

193209
#[DataProvider('provideCountryValues')]

tests/Functional/Uuid/UuidFilterBaseTestCase.php

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -142,13 +142,13 @@ public function testSearchFilterByInvalidUuid(): void
142142
$manager->persist($this->createDevice());
143143
$manager->flush();
144144

145-
$response = self::createClient()->request('GET', '/'.$this->getUrlPrefix().'_devices', [
145+
self::createClient()->request('GET', '/'.$this->getUrlPrefix().'_devices', [
146146
'query' => [
147147
'id' => 'invalid-uuid',
148148
],
149149
]);
150150

151-
$this->assertResponseStatusCodeSame(Response::HTTP_BAD_REQUEST);
151+
self::assertResponseStatusCodeSame(Response::HTTP_UNPROCESSABLE_ENTITY);
152152
}
153153

154154
public function testSearchFilterByManyInvalidUuid(): void
@@ -160,13 +160,13 @@ public function testSearchFilterByManyInvalidUuid(): void
160160
$manager->persist($this->createDevice());
161161
$manager->flush();
162162

163-
$response = self::createClient()->request('GET', '/'.$this->getUrlPrefix().'_devices', [
163+
self::createClient()->request('GET', '/'.$this->getUrlPrefix().'_devices', [
164164
'query' => [
165165
'id' => ['invalid-uuid', 'other-invalid-uuid'],
166166
],
167167
]);
168168

169-
$this->assertResponseStatusCodeSame(Response::HTTP_BAD_REQUEST);
169+
self::assertResponseStatusCodeSame(Response::HTTP_UNPROCESSABLE_ENTITY);
170170
}
171171

172172
public function testSearchFilterOnManyToOneRelationByUuid(): void
@@ -262,7 +262,7 @@ public function testSearchFilterOnManyToOneRelationByInvalidUuids(): void
262262
$manager->persist($this->createDeviceEndpoint(null, $bazDevice));
263263
$manager->flush();
264264

265-
$response = self::createClient()->request('GET', '/'.$this->getUrlPrefix().'_device_endpoints', [
265+
self::createClient()->request('GET', '/'.$this->getUrlPrefix().'_device_endpoints', [
266266
'query' => [
267267
'myDevice' => [
268268
'invalid-uuid',
@@ -271,7 +271,7 @@ public function testSearchFilterOnManyToOneRelationByInvalidUuids(): void
271271
],
272272
]);
273273

274-
$this->assertResponseStatusCodeSame(Response::HTTP_BAD_REQUEST);
274+
self::assertResponseStatusCodeSame(Response::HTTP_UNPROCESSABLE_ENTITY);
275275
}
276276

277277
public function testGetOpenApiDescription(): void

0 commit comments

Comments
 (0)