Skip to content

Commit 0326653

Browse files
committed
Add parser public API
1 parent de42d88 commit 0326653

17 files changed

+348
-43
lines changed

CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,18 @@ All Notable changes to `bakame/http-strucured-fields` will be documented in this
1616
- `ParameterAccess::replaceParamater`
1717
- `ParameterAccess::withoutParametersByKeys`
1818
- `ParameterAccess::withoutParametersByIndices`
19+
- `ItemParser` interface to return an array representation of a Structured Field as an item.
20+
- `ParametersParser` interface to return an array representation of a Structured Field parameter container.
21+
- `DictionaryParser` interface to return an array representation of a Structured Field dictionary container.
22+
- `ListParser` interface to return an array representation of a Structured Field list container.
23+
- `InnerListParser` interface to return an array representation of a Structured Field inner list container.
24+
- `ValueParser` interface to return a PHP type of a Structured Field Value string representation.
25+
- `Parser` is now part of the public API
26+
- `Item::fromHttpValue` now has an optional second parameter to shift the parser implementation used
27+
- `Parameters::fromHttpValue` now has an optional second parameter to shift the parser implementation used
28+
- `Dictionary::fromHttpValue` now has an optional second parameter to shift the parser implementation used
29+
- `OuterList::fromHttpValue` now has an optional second parameter to shift the parser implementation used
30+
- `InnerList::fromHttpValue` now has an optional second parameter to shift the parser implementation used
1931

2032
### Fixed
2133

README.md

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,56 @@ All five (5) structured data type as defined in the RFC are provided inside the
113113
- `OuterList` (named `List` in the RFC but renamed in the package because `list` is a reserved word in PHP.)
114114
- `InnerList`
115115

116+
#### Advance usage
117+
118+
In order to allow:
119+
120+
- clearer decoupling betwen parsing and objet building
121+
- different parsers implementations
122+
- improve the package usage in testing.
123+
124+
Starting with version `1.1` the internal parser has been made public. The class exposes
125+
the following method each belonging to a different contract or interface.
126+
127+
```php
128+
Parser::parseValue(Stringable|string $httpValue): ByteSequence|Token|DateTimeImmutable|string|int|float|bool;
129+
Parser::parseItem(Stringable|string $httpValue): array;
130+
Parser::parseParameters(Stringable|string $httpValue): array;
131+
Parser::parseInnerList(Stringable|string $httpValue): array;
132+
Parser::parseList(Stringable|string $httpValue): array;
133+
Parser::parseDictionary(Stringable|string $httpValue): array;
134+
```
135+
136+
While the provided default `Parser` class implements all these methods
137+
you are free to only implement the methods you need.
138+
139+
```php
140+
use Bakame\Http\StructuredFields\Parser;
141+
142+
$parser = new Parser();
143+
$parser->parseValue('text/csv'); //returns Token::fromString('text/csv')
144+
$parser->parseItem('@1234567890;file=24');
145+
//returns an array
146+
// [
147+
// new DateTimeImmutable('@1234567890'),
148+
// ['file' => 24],
149+
// ]
150+
```
151+
152+
Each `fromHttpValue` method signature has been updated to take a second optional argument
153+
that represents the parser interface to use in order to allow parsing of the HTTP string
154+
representation value.
155+
156+
By default, if no parser is provided, the package will default to use the package `Parser` class,
157+
158+
```php
159+
Item::fromHttpValue(Stringable|string $httpValue, ItemParser $parser = new Parser()): Item;
160+
InnerList::fromHttpValue(Stringable|string $httpValue, InnerListParser $parser = new Parser()): InnerList;
161+
Dictionary::fromHttpValue(Stringable|string $httpValue, DictionaryParser $parser = new Parser()): Dictionary;
162+
OuterList::fromHttpValue(Stringable|string $httpValue, ListParser $parser = new Parser()): OuterList;
163+
Parameters::fromHttpValue(Stringable|string $httpValue, ParametersParser $parser = new Parser()): Parameters;
164+
```
165+
116166
### Accessing Structured Fields Values
117167

118168
#### RFC Value type

src/Dictionary.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -108,17 +108,17 @@ public static function fromPairs(iterable $pairs): self
108108
*
109109
* @throws SyntaxError If the string is not a valid
110110
*/
111-
public static function fromHttpValue(Stringable|string $httpValue): self
111+
public static function fromHttpValue(Stringable|string $httpValue, DictionaryParser $parser = new Parser()): self
112112
{
113113
$converter = fn (array $member): InnerList|Item => match (true) {
114114
is_array($member[0]) => InnerList::fromAssociative(
115115
array_map(fn (array $item) => Item::fromAssociative(...$item), $member[0]),
116116
$member[1]
117117
),
118-
default => Item::fromAssociative($member[0], $member[1]),
118+
default => Item::fromAssociative(...$member),
119119
};
120120

121-
return new self(array_map($converter, Parser::parseDictionary($httpValue)));
121+
return new self(array_map($converter, $parser->parseDictionary($httpValue)));
122122
}
123123

124124
public function toHttpValue(): string

src/DictionaryParser.php

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Bakame\Http\StructuredFields;
6+
7+
use Stringable;
8+
9+
/**
10+
* @phpstan-import-type SfType from StructuredField
11+
*/
12+
interface DictionaryParser
13+
{
14+
/**
15+
* Returns an ordered map represented as a PHP associative array from an HTTP textual representation.
16+
*
17+
* @see https://www.rfc-editor.org/rfc/rfc8941.html#section-4.2.2
18+
*
19+
* @return array<string, array{0:SfType|array<array{0:SfType, 1:array<string, SfType>}>, 1:array<string, SfType>}>
20+
*/
21+
public function parseDictionary(Stringable|string $httpValue): array;
22+
}

src/InnerList.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,12 +59,12 @@ private static function filterMember(mixed $member): object
5959
*
6060
* @see https://www.rfc-editor.org/rfc/rfc8941.html#section-3.1
6161
*/
62-
public static function fromHttpValue(Stringable|string $httpValue): self
62+
public static function fromHttpValue(Stringable|string $httpValue, InnerListParser $parser = new Parser()): self
6363
{
64-
[$membersList, $parameters] = Parser::parseInnerList($httpValue);
64+
[$members, $parameters] = $parser->parseInnerList($httpValue);
6565

6666
return new self(
67-
array_map(fn (array $member): Item => Item::fromAssociative(...$member), $membersList),
67+
array_map(fn (array $member): Item => Item::fromAssociative(...$member), $members),
6868
Parameters::fromAssociative($parameters)
6969
);
7070
}

src/InnerListParser.php

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Bakame\Http\StructuredFields;
6+
7+
use Stringable;
8+
9+
/**
10+
* @phpstan-import-type SfType from StructuredField
11+
*/
12+
interface InnerListParser
13+
{
14+
/**
15+
* Returns an inner list represented as a PHP list array from an HTTP textual representation.
16+
*
17+
* @see https://www.rfc-editor.org/rfc/rfc8941.html#section-4.2.1.2
18+
*
19+
* @return array{0:array<array{0:SfType, 1:array<string, SfType>}>, 1:array<string, SfType>}
20+
*/
21+
public function parseInnerList(Stringable|string $httpValue): array;
22+
}

src/Item.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,9 @@ private function __construct(
3232
*
3333
* @throws SyntaxError If the HTTP value can not be parsed
3434
*/
35-
public static function fromHttpValue(Stringable|string $httpValue): self
35+
public static function fromHttpValue(Stringable|string $httpValue, ItemParser $parser = new Parser()): self
3636
{
37-
return self::fromAssociative(...Parser::parseItem($httpValue));
37+
return self::fromAssociative(...$parser->parseItem($httpValue));
3838
}
3939

4040
/**

src/ItemParser.php

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Bakame\Http\StructuredFields;
6+
7+
use Stringable;
8+
9+
/**
10+
* @phpstan-import-type SfType from StructuredField
11+
*/
12+
interface ItemParser
13+
{
14+
/**
15+
* Returns an Item represented as a PHP list array from an HTTP textual representation.
16+
*
17+
* @see https://www.rfc-editor.org/rfc/rfc8941.html#name-parsing-an-item
18+
*
19+
* @return array{0:SfType, 1:array<string, SfType>}
20+
*/
21+
public function parseItem(Stringable|string $httpValue): array;
22+
}

src/ListParser.php

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Bakame\Http\StructuredFields;
6+
7+
use Stringable;
8+
9+
/**
10+
* @phpstan-import-type SfType from StructuredField
11+
*/
12+
interface ListParser
13+
{
14+
/**
15+
* Returns an ordered list represented as a PHP list array from an HTTP textual representation.
16+
*
17+
* @see https://www.rfc-editor.org/rfc/rfc8941.html#section-4.2.1
18+
*
19+
* @return array<array{0:SfType|array<array{0:SfType, 1:array<string, SfType>}>, 1:array<string, SfType>}>
20+
*/
21+
public function parseList(Stringable|string $httpValue): array;
22+
}

src/OuterList.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,17 +60,17 @@ private static function filterMember(mixed $member): object
6060
*
6161
* @see https://www.rfc-editor.org/rfc/rfc8941.html#section-3.1
6262
*/
63-
public static function fromHttpValue(Stringable|string $httpValue): self
63+
public static function fromHttpValue(Stringable|string $httpValue, ListParser $parser = new Parser()): self
6464
{
6565
$converter = fn (array $member): InnerList|Item => match (true) {
6666
is_array($member[0]) => InnerList::fromAssociative(
6767
array_map(fn (array $item) => Item::fromAssociative(...$item), $member[0]),
6868
$member[1]
6969
),
70-
default => Item::fromAssociative($member[0], $member[1]),
70+
default => Item::fromAssociative(...$member),
7171
};
7272

73-
return new self(...array_map($converter, Parser::parseList($httpValue)));
73+
return new self(...array_map($converter, $parser->parseList($httpValue)));
7474
}
7575

7676
/**

src/Parameters.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,9 +105,9 @@ public static function fromPairs(iterable $pairs): self
105105
*
106106
* @throws SyntaxError If the string is not a valid
107107
*/
108-
public static function fromHttpValue(Stringable|string $httpValue): self
108+
public static function fromHttpValue(Stringable|string $httpValue, ParametersParser $parser = new Parser()): self
109109
{
110-
return new self(Parser::parseParameters($httpValue));
110+
return new self($parser->parseParameters($httpValue));
111111
}
112112

113113
public function toHttpValue(): string

src/ParametersParser.php

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Bakame\Http\StructuredFields;
6+
7+
use Stringable;
8+
9+
/**
10+
* @phpstan-import-type SfType from StructuredField
11+
*/
12+
interface ParametersParser
13+
{
14+
/**
15+
* Returns an array representation from an HTTP textual representation.
16+
*
17+
* @see https://www.rfc-editor.org/rfc/rfc8941.html#section-3.1.2
18+
*
19+
* @throws SyntaxError If the string is not a valid
20+
*
21+
* @return array<string, SfType>
22+
*/
23+
public function parseParameters(Stringable|string $httpValue): array;
24+
}

src/Parser.php

Lines changed: 28 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,15 @@
2222
*
2323
* @see https://www.rfc-editor.org/rfc/rfc8941.html#section-4.2
2424
*
25-
* @internal Use Dictionary::fromHttpValue(),
26-
* Parameters::fromHttpValue(),
27-
* OuterList::fromHttpValue(),
28-
* InnerList::fromHttpValue()
29-
* or Item::fromHttpValue() instead
25+
* @see Dictionary::fromHttpValue()
26+
* @see Parameters::fromHttpValue()
27+
* @see OuterList::fromHttpValue()
28+
* @see InnerList::fromHttpValue()
29+
* @see Item::fromHttpValue()
3030
*
3131
* @phpstan-import-type SfType from StructuredField
3232
*/
33-
final class Parser
33+
final class Parser implements DictionaryParser, InnerListParser, ItemParser, ListParser, ParametersParser, ValueParser
3434
{
3535
private const REGEXP_BYTE_SEQUENCE = '/^(?<sequence>:(?<byte>[a-z\d+\/=]*):)/i';
3636
private const REGEXP_BOOLEAN = '/^\?[01]/';
@@ -44,10 +44,25 @@ final class Parser
4444
private const FIRST_CHARACTER_RANGE_NUMBER = '-1234567890';
4545
private const FIRST_CHARACTER_RANGE_TOKEN = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ*';
4646

47+
public function parseValue(Stringable|string $httpValue): ByteSequence|Token|DateTimeImmutable|string|int|float|bool
48+
{
49+
$itemString = trim((string) $httpValue, ' ');
50+
if ('' === $itemString || 1 === preg_match(self::REGEXP_INVALID_CHARACTERS, $itemString)) {
51+
throw new SyntaxError('The HTTP textual representation "'.$httpValue.'" for an item value contains invalid characters.');
52+
}
53+
54+
[$value, $offset] = self::extractValue($itemString);
55+
if ('' !== substr($itemString, $offset)) {
56+
throw new SyntaxError('The HTTP textual representation "'.$httpValue.'" for an item value contains invalid characters.');
57+
}
58+
59+
return $value;
60+
}
61+
4762
/**
4863
* @return array{0:SfType, 1:array<string, SfType>}
4964
*/
50-
public static function parseItem(Stringable|string $httpValue): array
65+
public function parseItem(Stringable|string $httpValue): array
5166
{
5267
$itemString = trim((string) $httpValue, ' ');
5368
if ('' === $itemString || 1 === preg_match(self::REGEXP_INVALID_CHARACTERS, $itemString)) {
@@ -60,7 +75,7 @@ public static function parseItem(Stringable|string $httpValue): array
6075
throw new SyntaxError('The HTTP textual representation "'.$httpValue.'" for an item contains invalid characters.');
6176
}
6277

63-
return [$value, self::parseParameters($remainder)];
78+
return [$value, $this->parseParameters($remainder)];
6479
}
6580

6681
/**
@@ -72,7 +87,7 @@ public static function parseItem(Stringable|string $httpValue): array
7287
*
7388
* @return array<string, SfType>
7489
*/
75-
public static function parseParameters(Stringable|string $httpValue): array
90+
public function parseParameters(Stringable|string $httpValue): array
7691
{
7792
$parameterString = trim((string) $httpValue);
7893
[$parameters, $offset] = self::extractParametersValues($parameterString);
@@ -90,7 +105,7 @@ public static function parseParameters(Stringable|string $httpValue): array
90105
*
91106
* @return array<array{0:SfType|array<array{0:SfType, 1:array<string, SfType>}>, 1:array<string, SfType>}>
92107
*/
93-
public static function parseList(Stringable|string $httpValue): array
108+
public function parseList(Stringable|string $httpValue): array
94109
{
95110
$list = [];
96111
$remainder = ltrim((string) $httpValue, ' ');
@@ -109,7 +124,7 @@ public static function parseList(Stringable|string $httpValue): array
109124
*
110125
* @return array<string, array{0:SfType|array<array{0:SfType, 1:array<string, SfType>}>, 1:array<string, SfType>}>
111126
*/
112-
public static function parseDictionary(Stringable|string $httpValue): array
127+
public function parseDictionary(Stringable|string $httpValue): array
113128
{
114129
$map = [];
115130
$remainder = ltrim((string) $httpValue, ' ');
@@ -134,7 +149,7 @@ public static function parseDictionary(Stringable|string $httpValue): array
134149
*
135150
* @return array{0:array<array{0:SfType, 1:array<string, SfType>}>, 1:array<string, SfType>}
136151
*/
137-
public static function parseInnerList(Stringable|string $httpValue): array
152+
public function parseInnerList(Stringable|string $httpValue): array
138153
{
139154
$remainder = ltrim((string) $httpValue, ' ');
140155
if ('(' !== $remainder[0]) {
@@ -264,7 +279,7 @@ private static function extractValue(string $httpValue): array
264279
'@' === $httpValue[0] => self::extractDate($httpValue),
265280
str_contains(self::FIRST_CHARACTER_RANGE_NUMBER, $httpValue[0]) => self::extractNumber($httpValue),
266281
str_contains(self::FIRST_CHARACTER_RANGE_TOKEN, $httpValue[0]) => self::extractToken($httpValue),
267-
default => throw new SyntaxError("The HTTP textual representation \"$httpValue\" for an Item is unknown or unsupported."),
282+
default => throw new SyntaxError("The HTTP textual representation \"$httpValue\" for an item value is unknown or unsupported."),
268283
};
269284
}
270285

0 commit comments

Comments
 (0)