Skip to content

Commit e57dc8d

Browse files
committed
Simplify code using a MapKey value object
1 parent 7e26b10 commit e57dc8d

File tree

4 files changed

+63
-64
lines changed

4 files changed

+63
-64
lines changed

src/Dictionary.php

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
use function count;
1414
use function implode;
1515
use function is_array;
16-
use function preg_match;
1716

1817
/**
1918
* @implements IteratorAggregate<array-key, Item|InnerList>
@@ -227,23 +226,11 @@ public function pair(int $index): array
227226
*/
228227
public function set(string $key, InnerList|Item|ByteSequence|Token|bool|int|float|string $member): self
229228
{
230-
$this->members[self::filterKey($key)] = self::filterMember($member);
229+
$this->members[MapKey::fromString($key)->value] = self::filterMember($member);
231230

232231
return $this;
233232
}
234233

235-
/**
236-
* Validates the instance key against RFC8941 rules.
237-
*/
238-
private static function filterKey(string $key): string
239-
{
240-
if (1 !== preg_match('/^[a-z*][a-z0-9.*_-]*$/', $key)) {
241-
throw new SyntaxError("The dictionary key `$key` contains invalid characters.");
242-
}
243-
244-
return $key;
245-
}
246-
247234
private static function filterMember(InnerList|Item|ByteSequence|Token|bool|int|float|string $member): InnerList|Item
248235
{
249236
return match (true) {
@@ -283,7 +270,7 @@ public function append(string $key, InnerList|Item|ByteSequence|Token|bool|int|f
283270
{
284271
unset($this->members[$key]);
285272

286-
$this->members[self::filterKey($key)] = self::filterMember($member);
273+
$this->members[MapKey::fromString($key)->value] = self::filterMember($member);
287274

288275
return $this;
289276
}
@@ -297,7 +284,7 @@ public function prepend(string $key, InnerList|Item|ByteSequence|Token|bool|int|
297284
{
298285
unset($this->members[$key]);
299286

300-
$this->members = [...[self::filterKey($key) => self::filterMember($member)], ...$this->members];
287+
$this->members = [...[MapKey::fromString($key)->value => self::filterMember($member)], ...$this->members];
301288

302289
return $this;
303290
}

src/MapKey.php

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?php
2+
3+
namespace Bakame\Http\StructuredFields;
4+
5+
use function preg_match;
6+
7+
/**
8+
* @see https://www.rfc-editor.org/rfc/rfc8941.html#section-3.1.2
9+
* @internal normalize HTTP field key
10+
*/
11+
final class MapKey
12+
{
13+
private const REGEXP_KEY = '/^(?<key>[a-z*][a-z0-9.*_-]*)/';
14+
15+
private function __construct(
16+
public readonly string $value
17+
) {
18+
}
19+
20+
/**
21+
* @throws SyntaxError If the string is not a valid HTTP value field key
22+
*/
23+
public static function fromString(string $httpValue): self
24+
{
25+
$instance = self::fromStringBeginning($httpValue);
26+
if ($instance->value !== $httpValue) {
27+
throw new SyntaxError("No valid http value key could be extracted from `$httpValue`.");
28+
}
29+
30+
return $instance;
31+
}
32+
33+
/**
34+
* @throws SyntaxError If the string does not start with a valid HTTP value field key
35+
*/
36+
public static function fromStringBeginning(string $httpValue): self
37+
{
38+
if (1 !== preg_match(self::REGEXP_KEY, $httpValue, $found)) {
39+
throw new SyntaxError("No valid http value key could be extracted from `$httpValue`.");
40+
}
41+
42+
return new self($found['key']);
43+
}
44+
}

src/Parameters.php

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
use function count;
1313
use function explode;
1414
use function ltrim;
15-
use function preg_match;
1615
use function rtrim;
1716
use function trim;
1817

@@ -35,18 +34,6 @@ public static function __set_state(array $properties): self
3534
return new self($properties['members']);
3635
}
3736

38-
/**
39-
* @throws SyntaxError If the string is not a valid
40-
*/
41-
private static function filterKey(string $key): string
42-
{
43-
if (1 !== preg_match('/^[a-z*][a-z0-9.*_-]*$/', $key)) {
44-
throw new SyntaxError("The Parameters key `$key` contains invalid characters.");
45-
}
46-
47-
return $key;
48-
}
49-
5037
/**
5138
* @throws ForbiddenStateError If the bare item contains parameters
5239
*/
@@ -324,7 +311,7 @@ public function pair(int $index): array
324311
*/
325312
public function set(string $key, Item|ByteSequence|Token|bool|int|float|string $member): self
326313
{
327-
$this->members[self::filterKey($key)] = self::formatMember($member);
314+
$this->members[MapKey::fromString($key)->value] = self::formatMember($member);
328315

329316
return $this;
330317
}
@@ -361,7 +348,7 @@ public function append(string $key, Item|ByteSequence|Token|bool|int|float|strin
361348
{
362349
unset($this->members[$key]);
363350

364-
$this->members[self::filterKey($key)] = self::formatMember($member);
351+
$this->members[MapKey::fromString($key)->value] = self::formatMember($member);
365352

366353
return $this;
367354
}
@@ -376,7 +363,7 @@ public function prepend(string $key, Item|ByteSequence|Token|bool|int|float|stri
376363
{
377364
unset($this->members[$key]);
378365

379-
$this->members = [...[self::filterKey($key) => self::formatMember($member)], ...$this->members];
366+
$this->members = [...[MapKey::fromString($key)->value => self::formatMember($member)], ...$this->members];
380367

381368
return $this;
382369
}

src/Parser.php

Lines changed: 13 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66

77
use function in_array;
88
use function ltrim;
9-
use function ord;
109
use function preg_match;
1110
use function strlen;
1211
use function substr;
@@ -29,8 +28,8 @@ final class Parser
2928
* @see https://www.rfc-editor.org/rfc/rfc8941.html#section-4.2.1
3029
*
3130
* @return array<array{
32-
* 0:array<Item|ByteSequence|Token|bool|int|float|string>,
33-
* 1:array<string,Item|ByteSequence|Token|bool|int|float|string>
31+
* 0:array<Item|ByteSequence|Token|bool|int|float|string>,
32+
* 1:array<string,Item|ByteSequence|Token|bool|int|float|string>
3433
* }|Item|ByteSequence|Token|bool|int|float|string>
3534
*/
3635
public static function parseList(string $httpValue): array
@@ -51,17 +50,17 @@ public static function parseList(string $httpValue): array
5150
* @see https://www.rfc-editor.org/rfc/rfc8941.html#section-4.2.2
5251
*
5352
* @return array<string, Item|ByteSequence|Token|array{
54-
* 0:array<Item|ByteSequence|Token|bool|int|float|string>,
55-
* 1:array<string,Item|ByteSequence|Token|bool|int|float|string>
53+
* 0:array<Item|ByteSequence|Token|bool|int|float|string>,
54+
* 1:array<string,Item|ByteSequence|Token|bool|int|float|string>
5655
* }|bool|int|float|string>
5756
*/
5857
public static function parseDictionary(string $httpValue): array
5958
{
6059
$map = [];
6160
$remainder = ltrim($httpValue, ' ');
6261
while ('' !== $remainder) {
63-
[$key, $offset] = self::parseKey($remainder);
64-
$remainder = substr($remainder, $offset);
62+
$key = MapKey::fromStringBeginning($remainder)->value;
63+
$remainder = substr($remainder, strlen($key));
6564
if ('' === $remainder || '=' !== $remainder[0]) {
6665
$remainder = '=?1'.$remainder;
6766
}
@@ -79,8 +78,8 @@ public static function parseDictionary(string $httpValue): array
7978
* @see https://www.rfc-editor.org/rfc/rfc8941.html#section-4.2.1.2
8079
*
8180
* @return array{
82-
* 0:array<Item|ByteSequence|Token|bool|int|float|string>,
83-
* 1:array<string,Item|ByteSequence|Token|bool|int|float|string>
81+
* 0:array<Item|ByteSequence|Token|bool|int|float|string>,
82+
* 1:array<string,Item|ByteSequence|Token|bool|int|float|string>
8483
* }
8584
*/
8685
public static function parseInnerList(string $httpValue): array
@@ -92,7 +91,6 @@ public static function parseInnerList(string $httpValue): array
9291

9392
[$list, $offset] = self::parseInnerListValue($remainder);
9493
$remainder = self::removeOptionalWhiteSpaces(substr($remainder, $offset));
95-
9694
if ('' !== $remainder) {
9795
throw new SyntaxError("The HTTP textual representation `$httpValue` for a inner list contains invalid data.");
9896
}
@@ -140,8 +138,8 @@ private static function removeOptionalWhiteSpaces(string $httpValue): string
140138
* @see https://www.rfc-editor.org/rfc/rfc8941.html#section-4.2.1.1
141139
*
142140
* @return array{0: array{
143-
* 0:array<Item|ByteSequence|Token|bool|int|float|string>,
144-
* 1:array<string,Item|ByteSequence|Token|bool|int|float|string>
141+
* 0:array<Item|ByteSequence|Token|bool|int|float|string>,
142+
* 1:array<string,Item|ByteSequence|Token|bool|int|float|string>
145143
* }|Item, 1:int}
146144
*/
147145
private static function parseItemOrInnerList(string $httpValue): array
@@ -191,7 +189,6 @@ private static function parseInnerListValue(string $httpValue): array
191189
$remainder = substr($remainder, $offset);
192190

193191
$list[] = Item::from($value, $parameters);
194-
195192
if ('' !== $remainder && !in_array($remainder[0], [' ', ')'], true)) {
196193
throw new SyntaxError("The HTTP textual representation `$remainder` for a inner list is using invalid characters.");
197194
}
@@ -233,10 +230,10 @@ private static function parseParameters(string $httpValue): array
233230
while ('' !== $remainder && ';' === $remainder[0]) {
234231
$remainder = ltrim(substr($remainder, 1), ' ');
235232

236-
[$key, $keyOffset] = self::parseKey($remainder);
233+
$key = MapKey::fromStringBeginning($remainder)->value;
237234
$map[$key] = true;
238235

239-
$remainder = substr($remainder, $keyOffset);
236+
$remainder = substr($remainder, strlen($key));
240237
if ('' !== $remainder && '=' === $remainder[0]) {
241238
$remainder = substr($remainder, 1);
242239

@@ -248,22 +245,6 @@ private static function parseParameters(string $httpValue): array
248245
return [$map, strlen($httpValue) - strlen($remainder)];
249246
}
250247

251-
/**
252-
* Returns a Dictionary or a Parameter string key from an HTTP textual representation and the consumed offset in a tuple.
253-
*
254-
* @see https://www.rfc-editor.org/rfc/rfc8941.html#section-4.2.3.3
255-
*
256-
* @return array{0:string, 1:int}
257-
*/
258-
private static function parseKey(string $httpValue): array
259-
{
260-
if (1 !== preg_match('/^(?<key>[a-z*][a-z0-9.*_-]*)/', $httpValue, $matches)) {
261-
throw new SyntaxError("Invalid character in the HTTP textual representation of a key `$httpValue`.");
262-
}
263-
264-
return [$matches['key'], strlen($matches['key'])];
265-
}
266-
267248
/**
268249
* Returns a boolean from an HTTP textual representation and the consumed offset in a tuple.
269250
*
@@ -319,7 +300,7 @@ private static function parseString(string $httpValue): array
319300
return [$output, $offset];
320301
}
321302

322-
if (ord($char) <= 0x1f || ord($char) >= 0x7f) {
303+
if (1 === preg_match("/[^\x20-\x7E]/", $char)) {
323304
throw new SyntaxError("Invalid character in the HTTP textual representation of a string `$httpValue`.");
324305
}
325306

0 commit comments

Comments
 (0)