Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit f4d6b16

Browse files
committedOct 30, 2024·
Improve validation mechanism
1 parent a8b74dd commit f4d6b16

File tree

12 files changed

+431
-83
lines changed

12 files changed

+431
-83
lines changed
 

‎CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,12 @@ All Notable changes to `bakame/http-strucured-fields` will be documented in this
1111
- methods `map`, `reduce`, `filter`, `sort` to `all containers classes.
1212
- `StructuredFieldProvider` interface
1313
- Added a validation mechanism to facilitate Item validation against a field definition.
14+
- `Item::parameterByKey` replaces `Item::parameter`
15+
- `Item::tryNew`
16+
- `Parameters::getByKey` replaces `Parameters::get`
17+
- `Parameters::getByIndex` replaces `Parameters::pair`
18+
- `Parameters::valueByKey`
19+
- `Parameters::valueByIndex`
1420

1521
### Fixed
1622

@@ -26,6 +32,8 @@ All Notable changes to `bakame/http-strucured-fields` will be documented in this
2632

2733
- `MemberContainer`, `MemberList`, `MemberOrderedMap`, and all the Parser related methods.
2834
- All deprecated methods during the version 1 cycle.
35+
- `Item::parameter` replaced by `Item::parameterByKey`
36+
2937

3038
## [1.3.0](https://github.com/bakame-php/http-structured-fields/compare/1.2.2...1.3.0) - 2024-01-05
3139

‎src/Item.php

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace Bakame\Http\StructuredFields;
66

7+
use Bakame\Http\StructuredFields\Validation\Violation;
78
use DateTimeImmutable;
89
use DateTimeInterface;
910
use DateTimeZone;
@@ -99,6 +100,20 @@ public static function new(mixed $value): self
99100
return self::fromValue(new Value($value));
100101
}
101102

103+
/**
104+
* Returns a new bare instance from value or null on error.
105+
*
106+
* @param SfItemInput|array{0:SfItemInput, 1:Parameters|iterable<array{0:string, 1:SfItemInput}>} $value
107+
*/
108+
public static function tryNew(mixed $value): ?self
109+
{
110+
try {
111+
return self::fromValue(new Value($value));
112+
} catch (SyntaxError) {
113+
return null;
114+
}
115+
}
116+
102117
/**
103118
* Returns a new bare instance from value.
104119
*/
@@ -252,7 +267,7 @@ public static function false(): self
252267
*
253268
* @param ?callable(SfType): (string|bool) $validate
254269
*
255-
* @throws ValidationError
270+
* @throws Violation
256271
*/
257272
public function value(?callable $validate = null): ByteSequence|Token|DisplayString|DateTimeImmutable|string|int|float|bool
258273
{
@@ -262,7 +277,7 @@ public function value(?callable $validate = null): ByteSequence|Token|DisplayStr
262277
$exceptionMessage = "The item value '{$this->value->serialize()}' failed validation.";
263278
}
264279

265-
throw new ValidationError($exceptionMessage);
280+
throw new Violation($exceptionMessage);
266281
}
267282

268283
return $value;

‎src/MapKey.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,15 @@ public static function from(string|int $httpValue): self
3232
return $instance;
3333
}
3434

35+
public static function tryFrom(string|int $httpValue): ?self
36+
{
37+
try {
38+
return self::from($httpValue);
39+
} catch (SyntaxError $e) {
40+
return null;
41+
}
42+
}
43+
3544
/**
3645
* @throws SyntaxError If the string does not start with a valid HTTP value field key
3746
*/

‎src/ParameterAccess.php

Lines changed: 14 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace Bakame\Http\StructuredFields;
66

7+
use Bakame\Http\StructuredFields\Validation\Violation;
78
use DateTimeImmutable;
89
use DateTimeInterface;
910

@@ -29,41 +30,36 @@ public function parameters(): Parameters
2930
*
3031
* @param ?callable(SfType): (bool|string) $validate
3132
*
32-
* @throws ValidationError if the validation fails
33+
* @throws Violation if the validation fails
3334
*
3435
* @return SfType|null
3536
*/
36-
public function parameter(
37+
public function parameterByKey(
3738
string $key,
3839
?callable $validate = null,
39-
bool $required = false,
40+
bool|string $required = false,
4041
ByteSequence|Token|DisplayString|DateTimeImmutable|string|int|float|bool|null $default = null
4142
): ByteSequence|Token|DisplayString|DateTimeImmutable|string|int|float|bool|null {
42-
try {
43-
return $this->parameters->get($key, $validate)->value();
44-
} catch (InvalidOffset $exception) {
45-
return !$required ? $default : throw new ValidationError("The required parameter '$key' is missing.", previous: $exception);
46-
}
43+
return $this->parameters->valueByKey($key, $validate, $required, $default);
4744
}
4845

4946
/**
5047
* Returns the member value and name as pair or an empty array if no members value exists.
5148
*
5249
* @param ?callable(SfType, string): (bool|string) $validate
50+
* @param array{0:string, 1:SfType}|array{} $default
5351
*
54-
* @throws ValidationError if the validation fails
52+
* @throws Violation if the validation fails
5553
*
5654
* @return array{0:string, 1:SfType}|array{}
5755
*/
58-
public function parameterByIndex(int $index, ?callable $validate = null, bool $required = false): array
59-
{
60-
try {
61-
$tuple = $this->parameters->pair($index, $validate);
62-
63-
return [$tuple[0], $tuple[1]->value()];
64-
} catch (InvalidOffset $exception) {
65-
return !$required ? [] : throw new ValidationError("The required parameter at position '$index' is missing.", previous: $exception);
66-
}
56+
public function parameterByIndex(
57+
int $index,
58+
?callable $validate = null,
59+
bool|string $required = false,
60+
array $default = []
61+
): array {
62+
return $this->parameters->valueByIndex($index, $validate, $required, $default);
6763
}
6864

6965
/**

‎src/Parameters.php

Lines changed: 121 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,12 @@
55
namespace Bakame\Http\StructuredFields;
66

77
use ArrayAccess;
8+
use Bakame\Http\StructuredFields\Validation\Result;
9+
use Bakame\Http\StructuredFields\Validation\Violation;
10+
use Bakame\Http\StructuredFields\Validation\ViolationList;
811
use CallbackFilterIterator;
912
use Countable;
13+
use DateTimeImmutable;
1014
use DateTimeInterface;
1115
use Iterator;
1216
use IteratorAggregate;
@@ -206,17 +210,17 @@ public function has(string|int ...$keys): bool
206210
/**
207211
* @param ?callable(SfType): (bool|string) $validate
208212
*
209-
* @throws ValidationError|InvalidOffset
213+
* @throws Violation|InvalidOffset
210214
*/
211-
public function get(string $key, ?callable $validate = null): Item
215+
public function getByName(string $key, ?callable $validate = null): Item
212216
{
213217
$value = $this->members[$key] ?? throw InvalidOffset::dueToKeyNotFound($key);
214218
if (null !== $validate && (true !== ($exceptionMessage = $validate($value->value())))) {
215219
if (false === $exceptionMessage || !is_string($exceptionMessage) || '' === trim($exceptionMessage)) { /* @phpstan-ignore-line */
216220
$exceptionMessage = "The parameter '$key' whose value is '{$value->toHttpValue()}' failed validation.";
217221
}
218222

219-
throw new ValidationError($exceptionMessage);
223+
throw new Violation($exceptionMessage);
220224
}
221225

222226
return $value;
@@ -253,11 +257,11 @@ private function filterIndex(int $index, int|null $max = null): int|null
253257
/**
254258
* @param ?callable(SfType, string): (bool|string) $validate
255259
*
256-
* @throws InvalidOffset|ValidationError
260+
* @throws InvalidOffset|Violation
257261
*
258262
* @return array{0:string, 1:Item}
259263
*/
260-
public function pair(int $index, ?callable $validate = null): array
264+
public function getByIndex(int $index, ?callable $validate = null): array
261265
{
262266
$foundOffset = $this->filterIndex($index) ?? throw InvalidOffset::dueToIndexNotFound($index);
263267

@@ -267,7 +271,7 @@ public function pair(int $index, ?callable $validate = null): array
267271
$exceptionMessage = "The parameter at position '$index' whose name is '$key' with the value '{$value->toHttpValue()}' failed validation.";
268272
}
269273

270-
throw new ValidationError($exceptionMessage);
274+
throw new Violation($exceptionMessage);
271275
}
272276

273277
return [$key, $value];
@@ -291,7 +295,7 @@ public function pair(int $index, ?callable $validate = null): array
291295
public function first(): array
292296
{
293297
try {
294-
return $this->pair(0);
298+
return $this->getByIndex(0);
295299
} catch (InvalidOffset) {
296300
return [];
297301
}
@@ -303,7 +307,7 @@ public function first(): array
303307
public function last(): array
304308
{
305309
try {
306-
return $this->pair(-1);
310+
return $this->getByIndex(-1);
307311
} catch (InvalidOffset) {
308312
return [];
309313
}
@@ -325,6 +329,114 @@ public function only(string ...$keys): bool
325329
return [] !== $keys;
326330
}
327331

332+
/**
333+
* Returns the member value or null if no members value exists.
334+
*
335+
* @param ?callable(SfType): (bool|string) $validate
336+
*
337+
* @throws Violation if the validation fails
338+
*
339+
* @return SfType|null
340+
*/
341+
public function valueByKey(
342+
string $key,
343+
?callable $validate = null,
344+
bool|string $required = false,
345+
ByteSequence|Token|DisplayString|DateTimeImmutable|string|int|float|bool|null $default = null
346+
): ByteSequence|Token|DisplayString|DateTimeImmutable|string|int|float|bool|null {
347+
try {
348+
return $this->getByName($key, $validate)->value();
349+
} catch (InvalidOffset $exception) {
350+
if (false === $required) {
351+
return $default;
352+
}
353+
354+
$message = $required;
355+
if (is_bool($message) || '' === trim($message)) {
356+
$message = "The required parameter '$key' is missing.";
357+
}
358+
359+
throw new Violation($message, previous: $exception);
360+
}
361+
}
362+
363+
/**
364+
* @param array<string, array{validate?:callable(SfType): (bool|string), required?:(bool|string), default:SfType|null}> $rules
365+
*/
366+
public function validateByKeys(array $rules): Result
367+
{
368+
$result = [];
369+
$violations = new ViolationList();
370+
371+
foreach ($rules as $key => $rule) {
372+
try {
373+
$result[$key] = $this->valueByKey($key, $rule['validate'] ?? null, $rule['required'] ?? false, $rule['default'] ?? null);
374+
} catch (Violation $exception) {
375+
$violations[$key] = $exception;
376+
}
377+
}
378+
379+
return new Result($result, $violations);
380+
}
381+
382+
/**
383+
* Returns the member value and name as pair or an empty array if no members value exists.
384+
*
385+
* @param ?callable(SfType, string): (bool|string) $validate
386+
* @param array{0:string, 1:SfType}|array{} $default
387+
*
388+
* @throws Violation if the validation fails
389+
*
390+
* @return array{0:string, 1:SfType}|array{}
391+
*/
392+
public function valueByIndex(int $index, ?callable $validate = null, bool|string $required = false, array $default = []): array
393+
{
394+
$default = match (true) {
395+
[] === $default => [],
396+
!array_is_list($default) => throw new SyntaxError('The pair must be represented by an array as a list.'), /* @phpstan-ignore-line */
397+
2 !== count($default) => throw new SyntaxError('The pair first member is the name; its second member is its value.'), /* @phpstan-ignore-line */
398+
null === ($key = MapKey::tryFrom($default[0])?->value) => throw new SyntaxError('The pair first member is invalid.'),
399+
null === ($value = Item::tryNew($default[1])?->value()) => throw new SyntaxError('The pair second member is invalid.'),
400+
default => [$key, $value],
401+
};
402+
403+
try {
404+
$tuple = $this->getByIndex($index, $validate);
405+
406+
return [$tuple[0], $tuple[1]->value()];
407+
} catch (InvalidOffset $exception) {
408+
if (false === $required) {
409+
return $default;
410+
}
411+
412+
$message = $required;
413+
if (is_bool($message) || '' === trim($message)) {
414+
$message = "The required parameter at position '$index' is missing.";
415+
}
416+
417+
throw new Violation($message, previous: $exception);
418+
}
419+
}
420+
421+
/**
422+
* @param array<int, array{validate?:callable(SfType, string): (bool|string), required?:(bool|string), default?:array{0:string, 1:SfType}|array{}}> $rules
423+
*/
424+
public function validateByIndices(array $rules): Result
425+
{
426+
$result = [];
427+
$violations = new ViolationList();
428+
429+
foreach ($rules as $index => $rule) {
430+
try {
431+
$result[$index] = $this->valueByIndex($index, $rule['validate'] ?? null, $rule['required'] ?? false, $rule['default'] ?? []);
432+
} catch (Violation $exception) {
433+
$violations[$index] = $exception;
434+
}
435+
}
436+
437+
return new Result($result, $violations);
438+
}
439+
328440
public function add(string $key, StructuredFieldProvider|StructuredField|Token|ByteSequence|DisplayString|DateTimeInterface|string|int|float|bool $member): static
329441
{
330442
$members = $this->members;
@@ -505,7 +617,7 @@ public function offsetExists(mixed $offset): bool
505617
*/
506618
public function offsetGet(mixed $offset): Item
507619
{
508-
return $this->get($offset);
620+
return $this->getByName($offset);
509621
}
510622

511623
public function offsetUnset(mixed $offset): void

0 commit comments

Comments
 (0)
Please sign in to comment.