5
5
namespace Bakame \Http \StructuredFields ;
6
6
7
7
use ArrayAccess ;
8
+ use Bakame \Http \StructuredFields \Validation \Result ;
9
+ use Bakame \Http \StructuredFields \Validation \Violation ;
10
+ use Bakame \Http \StructuredFields \Validation \ViolationList ;
8
11
use CallbackFilterIterator ;
9
12
use Countable ;
13
+ use DateTimeImmutable ;
10
14
use DateTimeInterface ;
11
15
use Iterator ;
12
16
use IteratorAggregate ;
@@ -206,17 +210,17 @@ public function has(string|int ...$keys): bool
206
210
/**
207
211
* @param ?callable(SfType): (bool|string) $validate
208
212
*
209
- * @throws ValidationError |InvalidOffset
213
+ * @throws Violation |InvalidOffset
210
214
*/
211
- public function get (string $ key , ?callable $ validate = null ): Item
215
+ public function getByName (string $ key , ?callable $ validate = null ): Item
212
216
{
213
217
$ value = $ this ->members [$ key ] ?? throw InvalidOffset::dueToKeyNotFound ($ key );
214
218
if (null !== $ validate && (true !== ($ exceptionMessage = $ validate ($ value ->value ())))) {
215
219
if (false === $ exceptionMessage || !is_string ($ exceptionMessage ) || '' === trim ($ exceptionMessage )) { /* @phpstan-ignore-line */
216
220
$ exceptionMessage = "The parameter ' $ key' whose value is ' {$ value ->toHttpValue ()}' failed validation. " ;
217
221
}
218
222
219
- throw new ValidationError ($ exceptionMessage );
223
+ throw new Violation ($ exceptionMessage );
220
224
}
221
225
222
226
return $ value ;
@@ -253,11 +257,11 @@ private function filterIndex(int $index, int|null $max = null): int|null
253
257
/**
254
258
* @param ?callable(SfType, string): (bool|string) $validate
255
259
*
256
- * @throws InvalidOffset|ValidationError
260
+ * @throws InvalidOffset|Violation
257
261
*
258
262
* @return array{0:string, 1:Item}
259
263
*/
260
- public function pair (int $ index , ?callable $ validate = null ): array
264
+ public function getByIndex (int $ index , ?callable $ validate = null ): array
261
265
{
262
266
$ foundOffset = $ this ->filterIndex ($ index ) ?? throw InvalidOffset::dueToIndexNotFound ($ index );
263
267
@@ -267,7 +271,7 @@ public function pair(int $index, ?callable $validate = null): array
267
271
$ exceptionMessage = "The parameter at position ' $ index' whose name is ' $ key' with the value ' {$ value ->toHttpValue ()}' failed validation. " ;
268
272
}
269
273
270
- throw new ValidationError ($ exceptionMessage );
274
+ throw new Violation ($ exceptionMessage );
271
275
}
272
276
273
277
return [$ key , $ value ];
@@ -291,7 +295,7 @@ public function pair(int $index, ?callable $validate = null): array
291
295
public function first (): array
292
296
{
293
297
try {
294
- return $ this ->pair (0 );
298
+ return $ this ->getByIndex (0 );
295
299
} catch (InvalidOffset ) {
296
300
return [];
297
301
}
@@ -303,7 +307,7 @@ public function first(): array
303
307
public function last (): array
304
308
{
305
309
try {
306
- return $ this ->pair (-1 );
310
+ return $ this ->getByIndex (-1 );
307
311
} catch (InvalidOffset ) {
308
312
return [];
309
313
}
@@ -325,6 +329,114 @@ public function only(string ...$keys): bool
325
329
return [] !== $ keys ;
326
330
}
327
331
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
+
328
440
public function add (string $ key , StructuredFieldProvider |StructuredField |Token |ByteSequence |DisplayString |DateTimeInterface |string |int |float |bool $ member ): static
329
441
{
330
442
$ members = $ this ->members ;
@@ -505,7 +617,7 @@ public function offsetExists(mixed $offset): bool
505
617
*/
506
618
public function offsetGet (mixed $ offset ): Item
507
619
{
508
- return $ this ->get ($ offset );
620
+ return $ this ->getByName ($ offset );
509
621
}
510
622
511
623
public function offsetUnset (mixed $ offset ): void
0 commit comments