Skip to content

Commit 2cea529

Browse files
committed
Adding validation mechanism
1 parent 8a47868 commit 2cea529

12 files changed

+159
-58
lines changed

src/Dictionary.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,7 @@ public function has(string|int ...$keys): bool
277277
* @throws SyntaxError If the key is invalid
278278
* @throws InvalidOffset If the key is not found
279279
*/
280-
public function get(string|int $key): InnerList|Item
280+
public function get(string $key): InnerList|Item
281281
{
282282
return $this->members[$key] ?? throw InvalidOffset::dueToKeyNotFound($key);
283283
}

src/InnerList.php

+47-10
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
/**
3030
* @see https://www.rfc-editor.org/rfc/rfc9651.html#section-3.1.1
3131
*
32+
* @phpstan-import-type SfType from StructuredField
3233
* @phpstan-import-type SfItemInput from StructuredField
3334
* @phpstan-import-type SfInnerListPair from StructuredField
3435
*
@@ -169,25 +170,61 @@ public function parameters(): Parameters
169170
return $this->parameters;
170171
}
171172

172-
public function parameter(string $key): Token|ByteSequence|DisplayString|DateTimeImmutable|int|float|string|bool|null
173-
{
174-
try {
175-
return $this->parameters->get($key)->value();
176-
} catch (StructuredFieldError) {
177-
return null;
173+
/**
174+
* @param ?Closure(SfType): ?string $validate
175+
* @param ?SfType $default
176+
*
177+
* @throws ValidationError
178+
*
179+
* @return ?SfType
180+
*/
181+
public function parameter(
182+
string $key,
183+
?Closure $validate = null,
184+
bool $required = false,
185+
ByteSequence|Token|DisplayString|DateTimeImmutable|string|int|float|bool|null $default = null
186+
): ByteSequence|Token|DisplayString|DateTimeImmutable|string|int|float|bool|null {
187+
if (!$this->parameters->has($key)) {
188+
!$required || throw new ValidationError("The parameter '$key' does not exist and is required.");
189+
190+
return $default;
191+
}
192+
193+
$value = $this->parameters->get($key)->value();
194+
if (null === $validate) {
195+
return $value;
178196
}
197+
198+
null === ($exceptionMessage = $validate($value)) || throw new ValidationError($exceptionMessage);
199+
200+
return $value;
179201
}
180202

181203
/**
182-
* @return array{0:string, 1:Token|ByteSequence|DisplayString|DateTimeImmutable|int|float|string|bool}|array{}
204+
* @param ?Closure(SfType, string): ?string $validate
205+
*
206+
* @throws ValidationError
207+
*
208+
* @return array{0:string, 1:SfType}|array{}
183209
*/
184-
public function parameterByIndex(int $index): array
210+
public function parameterByIndex(int $index, ?Closure $validate = null, bool $required = false): array
185211
{
186212
try {
187213
$tuple = $this->parameters->pair($index);
214+
if (null === $validate) {
215+
return [$tuple[0], $tuple[1]->value()];
216+
}
217+
218+
null === ($exceptionMessage = $validate($tuple[1]->value(), $tuple[0])) || throw new ValidationError($exceptionMessage);
188219

189220
return [$tuple[0], $tuple[1]->value()];
190-
} catch (StructuredFieldError) {
221+
} catch (StructuredFieldError $exception) {
222+
if ($exception instanceof ValidationError) {
223+
throw $exception;
224+
}
225+
226+
!$required || throw new ValidationError("The parameter at position '$index' does not exist and is required.");
227+
191228
return [];
192229
}
193230
}
@@ -244,7 +281,7 @@ private function filterIndex(string|int $index, int|null $max = null): int|null
244281
};
245282
}
246283

247-
public function get(string|int $key): Item
284+
public function get(int $key): Item
248285
{
249286
return $this->members[$this->filterIndex($key) ?? throw InvalidOffset::dueToIndexNotFound($key)];
250287
}

src/Item.php

+57-10
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
/**
1717
* @see https://www.rfc-editor.org/rfc/rfc9651.html#section-3.3
1818
*
19+
* @phpstan-import-type SfType from StructuredField
1920
* @phpstan-import-type SfItemInput from StructuredField
2021
* @phpstan-import-type SfItemPair from StructuredField
2122
* @phpstan-import-type SfTypeInput from StructuredField
@@ -243,9 +244,19 @@ public static function false(): self
243244

244245
/**
245246
* Returns the underlying value.
247+
* If a validation rule is provided, an exception will be thrown
248+
* if the validtion rules does not return null.
249+
*
250+
* @param ?Closure(SfType): ?string $validate
251+
*
252+
* @throws ValidationError
246253
*/
247-
public function value(): ByteSequence|Token|DisplayString|DateTimeImmutable|string|int|float|bool
254+
public function value(?Closure $validate = null): ByteSequence|Token|DisplayString|DateTimeImmutable|string|int|float|bool
248255
{
256+
(null === $validate)
257+
|| (null === ($exceptionMessage = $validate($this->value->value)))
258+
|| throw new ValidationError($exceptionMessage);
259+
249260
return $this->value->value;
250261
}
251262

@@ -259,25 +270,61 @@ public function parameters(): Parameters
259270
return $this->parameters;
260271
}
261272

262-
public function parameter(string $key): ByteSequence|Token|DisplayString|DateTimeImmutable|string|int|float|bool|null
263-
{
264-
try {
265-
return $this->parameters->get($key)->value();
266-
} catch (StructuredFieldError) {
267-
return null;
273+
/**
274+
* @param ?Closure(SfType): ?string $validate
275+
* @param ?SfType $default
276+
*
277+
* @throws ValidationError
278+
*
279+
* @return SfType|null
280+
*/
281+
public function parameter(
282+
string $key,
283+
?Closure $validate = null,
284+
bool $required = false,
285+
ByteSequence|Token|DisplayString|DateTimeImmutable|string|int|float|bool|null $default = null
286+
): ByteSequence|Token|DisplayString|DateTimeImmutable|string|int|float|bool|null {
287+
if (!$this->parameters->has($key)) {
288+
!$required || throw new ValidationError("The parameter '$key' does not exist and is required.");
289+
290+
return $default;
268291
}
292+
293+
$value = $this->parameters->get($key)->value();
294+
if (null === $validate) {
295+
return $value;
296+
}
297+
298+
null === ($exceptionMessage = $validate($value)) || throw new ValidationError($exceptionMessage);
299+
300+
return $value;
269301
}
270302

271303
/**
272-
* @return array{0:string, 1:Token|ByteSequence|DisplayString|DateTimeImmutable|int|float|string|bool}|array{}
304+
* @param ?Closure(SfType, string): ?string $validate
305+
*
306+
* @throws ValidationError
307+
*
308+
* @return array{0:string, 1:SfType}|array{}
273309
*/
274-
public function parameterByIndex(int $index): array
310+
public function parameterByIndex(int $index, ?Closure $validate = null, bool $required = false): array
275311
{
276312
try {
277313
$tuple = $this->parameters->pair($index);
314+
if (null === $validate) {
315+
return [$tuple[0], $tuple[1]->value()];
316+
}
317+
318+
null === ($exceptionMessage = $validate($tuple[1]->value(), $tuple[0])) || throw new ValidationError($exceptionMessage);
278319

279320
return [$tuple[0], $tuple[1]->value()];
280-
} catch (StructuredFieldError) {
321+
} catch (StructuredFieldError $exception) {
322+
if ($exception instanceof ValidationError) {
323+
throw $exception;
324+
}
325+
326+
!$required || throw new ValidationError("The parameter at position '$index' does not exist and is required.");
327+
281328
return [];
282329
}
283330
}

src/OuterList.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,7 @@ private function filterIndex(string|int $index, int|null $max = null): int|null
224224
};
225225
}
226226

227-
public function get(string|int $key): InnerList|Item
227+
public function get(int $key): InnerList|Item
228228
{
229229
return $this->members[$this->filterIndex($key) ?? throw InvalidOffset::dueToIndexNotFound($key)];
230230
}

src/ParameterAccess.php

+15-3
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
/**
1111
* Common manipulation methods used when interacting with an object
1212
* with a Parameters instance attached to it.
13+
*
14+
* @phpstan-import-type SfType from StructuredField
1315
*/
1416
interface ParameterAccess
1517
{
@@ -25,15 +27,25 @@ public function parameters(): Parameters;
2527

2628
/**
2729
* Returns the member value or null if no members value exists.
30+
*
31+
* @param ?Closure(SfType): ?string $validate
32+
*
33+
* @throws ValidationError if the validation fails
34+
*
35+
* @return SfType|null
2836
*/
29-
public function parameter(string $key): ByteSequence|Token|DisplayString|DateTimeImmutable|string|int|float|bool|null;
37+
public function parameter(string $key, ?Closure $validate = null, bool $required = false, ByteSequence|Token|DisplayString|DateTimeImmutable|string|int|float|bool|null $default = null): ByteSequence|Token|DisplayString|DateTimeImmutable|string|int|float|bool|null;
3038

3139
/**
3240
* Returns the member value and name as pair or an emoty array if no members value exists.
3341
*
34-
* @return array{0:string, 1:Token|ByteSequence|DisplayString|DateTimeImmutable|int|float|string|bool}|array{}
42+
* @param ?Closure(SfType, string): ?string $validate
43+
*
44+
* @throws ValidationError if the validation fails
45+
*
46+
* @return array{0:string, 1:SfType}|array{}
3547
*/
36-
public function parameterByIndex(int $index): array;
48+
public function parameterByIndex(int $index, ?Closure $validate = null, bool $required = false): array;
3749

3850
/**
3951
* Adds a member if its key is not present at the of the associated parameter instance or update the instance at the given key.

src/Parameters.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ public function has(string|int ...$keys): bool
206206
/**
207207
* @throws InvalidOffset If the key is not found
208208
*/
209-
public function get(string|int $key): Item
209+
public function get(string $key): Item
210210
{
211211
return $this->members[$key] ?? throw InvalidOffset::dueToKeyNotFound($key);
212212
}

src/ValidationError.php

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Bakame\Http\StructuredFields;
6+
7+
use RuntimeException;
8+
9+
final class ValidationError extends RuntimeException implements StructuredFieldError
10+
{
11+
}

tests/DictionaryTest.php

-8
Original file line numberDiff line numberDiff line change
@@ -263,14 +263,6 @@ public function it_can_delete_a_member_via_remove_method(): void
263263
self::assertSame($instanceWithoutMember->remove(), $instanceWithoutMember);
264264
}
265265

266-
#[Test]
267-
public function it_fails_to_fetch_an_value_using_an_integer(): void
268-
{
269-
$this->expectException(StructuredFieldError::class);
270-
271-
Dictionary::new()->get(0);
272-
}
273-
274266
#[Test]
275267
public function it_can_access_the_item_value(): void
276268
{

tests/InnerListTest.php

-8
Original file line numberDiff line numberDiff line change
@@ -182,14 +182,6 @@ public function it_returns_the_same_object_if_no_member_is_removed(): void
182182
self::assertCount(0, InnerList::new()->remove(0));
183183
}
184184

185-
#[Test]
186-
public function it_fails_to_fetch_an_value_using_an_integer(): void
187-
{
188-
$this->expectException(InvalidOffset::class);
189-
190-
InnerList::new()->get('zero');
191-
}
192-
193185
#[Test]
194186
public function it_can_access_the_item_value(): void
195187
{

tests/ItemTest.php

+26
Original file line numberDiff line numberDiff line change
@@ -470,4 +470,30 @@ public function it_can_create_a_new_instance_using_parameters_position_modifying
470470
self::assertSame(['c', 'true'], $instance2->parameterByIndex(-1));
471471
self::assertSame(';b=?0;d=*/*;a="false";c="true"', $instance2->parameters()->toHttpValue());
472472
}
473+
474+
#[Test]
475+
public function it_can_validate_the_item_value(): void
476+
{
477+
$item = Item::fromAssociative(Token::fromString('babayaga'), ['a' => true]);
478+
self::assertInstanceOf(Token::class, $item->value());
479+
self::assertTrue($item->value()->equals(Token::fromString('babayaga')));
480+
481+
$this->expectExceptionObject(new ValidationError('The exception has been triggered'));
482+
$item->value(fn (mixed $value): string => 'The exception has been triggered');
483+
}
484+
485+
#[Test]
486+
public function it_can_validate_the_item_parameter_value(): void
487+
{
488+
$item = Item::fromAssociative(Token::fromString('babayaga'), ['a' => true]);
489+
self::assertTrue($item->parameter('a'));
490+
self::assertTrue($item->parameter('a', fn (mixed $value): ?string => null));
491+
self::assertFalse($item->parameter(key: 'b', default: false));
492+
493+
$this->expectExceptionObject(new ValidationError('The exception has been triggered'));
494+
$item->parameter(
495+
key: 'a',
496+
validate:fn (mixed $value): string => 'The exception has been triggered',
497+
);
498+
}
473499
}

tests/OuterListTest.php

-8
Original file line numberDiff line numberDiff line change
@@ -165,14 +165,6 @@ public function it_returns_the_same_object_if_nothing_is_changed(): void
165165
self::assertSame($container, $sameContainer);
166166
}
167167

168-
#[Test]
169-
public function it_fails_to_fetch_an_value_using_an_integer(): void
170-
{
171-
$this->expectException(InvalidOffset::class);
172-
173-
OuterList::new()->get('zero');
174-
}
175-
176168
#[Test]
177169
public function it_can_access_the_item_value(): void
178170
{

tests/ParametersTest.php

-8
Original file line numberDiff line numberDiff line change
@@ -360,14 +360,6 @@ public function it_can_delete_a_member_via_array_access(): void
360360
self::assertSame($instanceWithoutMembers->remove(), $instanceWithoutMembers);
361361
}
362362

363-
#[Test]
364-
public function it_fails_to_fetch_an_value_using_an_integer(): void
365-
{
366-
$this->expectException(InvalidOffset::class);
367-
368-
Parameters::new()->get(0);
369-
}
370-
371363
#[Test]
372364
public function it_throws_if_the_structured_field_is_not_supported(): void
373365
{

0 commit comments

Comments
 (0)