Skip to content

Commit e1750d4

Browse files
committed
Improve Parser codebase test
1 parent 3ec261a commit e1750d4

File tree

5 files changed

+78
-16
lines changed

5 files changed

+78
-16
lines changed

src/Dictionary.php

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
use IteratorAggregate;
1010
use function array_key_exists;
1111
use function array_keys;
12-
use function array_values;
1312
use function count;
1413
use function implode;
1514
use function preg_match;
@@ -98,6 +97,9 @@ public function count(): int
9897
return count($this->members);
9998
}
10099

100+
/**
101+
* Tells whether the instance contains no member.
102+
*/
101103
public function isEmpty(): bool
102104
{
103105
return [] === $this->members;
@@ -133,11 +135,20 @@ public function keys(): array
133135
return array_keys($this->members);
134136
}
135137

138+
/**
139+
* Tells whether an item or an inner-list is attached to the given key.
140+
*/
136141
public function has(string $key): bool
137142
{
138143
return array_key_exists($key, $this->members);
139144
}
140145

146+
/**
147+
* Returns the item or the inner-list is attached to the given key otherwise throw.
148+
*
149+
* @throws SyntaxError If the key is invalid
150+
* @throws InvalidOffset If the key is not found
151+
*/
141152
public function get(string $key): Item|InnerList
142153
{
143154
self::validateKey($key);
@@ -149,11 +160,17 @@ public function get(string $key): Item|InnerList
149160
return $this->members[$key];
150161
}
151162

163+
/**
164+
* Tells whether an item or an inner-list and a key are attached to the given index position.
165+
*/
152166
public function hasPair(int $index): bool
153167
{
154168
return null !== $this->filterIndex($index);
155169
}
156170

171+
/**
172+
* Validate and Format the submitted index position.
173+
*/
157174
private function filterIndex(int $index): int|null
158175
{
159176
$max = count($this->members);
@@ -166,6 +183,12 @@ private function filterIndex(int $index): int|null
166183
}
167184

168185
/**
186+
* Returns the item or the inner-list and its key as attached to the given
187+
* collection according to their index position otherwise throw.
188+
*
189+
* @throws SyntaxError If the key is invalid
190+
* @throws InvalidOffset If the key is not found
191+
*
169192
* @return array{0:string, 1:Item|InnerList}
170193
*/
171194
public function pair(int $index): array
@@ -175,10 +198,14 @@ public function pair(int $index): array
175198
throw InvalidOffset::dueToIndexNotFound($index);
176199
}
177200

178-
return [
179-
array_keys($this->members)[$offset],
180-
array_values($this->members)[$offset],
181-
];
201+
foreach ($this->toPairs() as $k => $pair) {
202+
if ($k === $offset) {
203+
return $pair;
204+
}
205+
}
206+
// @codeCoverageIgnoreStart
207+
throw InvalidOffset::dueToIndexNotFound($index);
208+
// @codeCoverageIgnoreEnd
182209
}
183210

184211
/**
@@ -201,6 +228,9 @@ private static function validateKey(string $key): void
201228
}
202229
}
203230

231+
/**
232+
* Format the member type.
233+
*/
204234
private static function filterMember(InnerList|Item|ByteSequence|Token|bool|int|float|string $member): InnerList|Item
205235
{
206236
return match (true) {

src/InnerListTest.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,4 +162,22 @@ public function it_successfully_parse_a_http_field(): void
162162
self::assertSame(42.0, $instance->get(2)->value);
163163
self::assertInstanceOf(Token::class, $instance->get(2)->parameters->value('john'));
164164
}
165+
166+
/**
167+
* @test
168+
*/
169+
public function it_fails_to_parse_an_invalid_http_field(): void
170+
{
171+
$this->expectException(SyntaxError::class);
172+
InnerList::fromHttpValue('("hello)world" 42 42.0;john=doe);foo="bar(" toto');
173+
}
174+
175+
/**
176+
* @test
177+
*/
178+
public function it_fails_to_parse_an_invalid_http_field_2(): void
179+
{
180+
$this->expectException(SyntaxError::class);
181+
InnerList::fromHttpValue('"hello)world" 42 42.0;john=doe);foo="bar("');
182+
}
165183
}

src/Item.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -107,9 +107,9 @@ public static function fromHttpValue(string $httpValue): self
107107
1 === preg_match("/[\r\t\n]/", $httpValue),
108108
1 === preg_match("/[^\x20-\x7E]/", $httpValue) => throw new SyntaxError("The HTTP textual representation `$httpValue` for an item contains invalid characters."),
109109
1 === preg_match('/^(-?[0-9])/', $httpValue) => self::parseNumber($httpValue),
110-
$httpValue[0] == '"' => self::parseString($httpValue),
111-
$httpValue[0] == ':' => self::parseBytesSequence($httpValue),
112-
$httpValue[0] == '?' => self::parseBoolean($httpValue),
110+
'"' === $httpValue[0] => self::parseString($httpValue),
111+
':' === $httpValue[0] => self::parseBytesSequence($httpValue),
112+
'?' === $httpValue[0] => self::parseBoolean($httpValue),
113113
1 === preg_match('/^([a-z*])/i', $httpValue) => self::parseToken($httpValue),
114114
default => throw new SyntaxError("The HTTP textual representation `$httpValue` for an item is unknown or unsupported."),
115115
};

src/OrderedListTest.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,4 +144,24 @@ public function test_it_can_generate_the_same_value(): void
144144

145145
self::assertSame($res->toHttpValue(), $list->toHttpValue());
146146
}
147+
148+
/**
149+
* @test
150+
*/
151+
public function it_fails_to_parse_invalid_string_1(): void
152+
{
153+
$this->expectException(SyntaxError::class);
154+
155+
OrderedList::fromHttpValue('(foo;number="hello\")');
156+
}
157+
158+
/**
159+
* @test
160+
*/
161+
public function it_fails_to_parse_invalid_string_2(): void
162+
{
163+
$this->expectException(SyntaxError::class);
164+
165+
Dictionary::fromHttpValue('number="hell\o"');
166+
}
147167
}

src/Parser.php

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ private static function parseBareItem(string $httpValue): array
187187
return match (true) {
188188
'' === $httpValue => throw new SyntaxError('Unexpected empty string for The HTTP textual representation of an item.'),
189189
1 === preg_match('/^(-|\d)/', $httpValue) => self::parseNumber($httpValue),
190-
'"' === $httpValue[0] => self::parseString($httpValue),
190+
'"' === $httpValue[0] => self::parseString($httpValue),
191191
':' === $httpValue[0] => self::parseByteSequence($httpValue),
192192
'?' === $httpValue[0] => self::parseBoolean($httpValue),
193193
1 === preg_match('/^([a-z*])/i', $httpValue) => self::parseToken($httpValue),
@@ -265,9 +265,7 @@ private static function parseBoolean(string $httpValue): array
265265
*/
266266
private static function parseNumber(string $httpValue): array
267267
{
268-
if (1 !== preg_match('/^(?<number>-?\d+(?:\.\d+)?)(?:[^\d.]|$)/', $httpValue, $found)) {
269-
throw new SyntaxError("Invalid number format in the HTTP textual representation of a number value `$httpValue`.");
270-
}
268+
preg_match('/^(?<number>-?\d+(?:\.\d+)?)(?:[^\d.]|$)/', $httpValue, $found);
271269

272270
return match (true) {
273271
1 === preg_match('/^-?\d{1,12}\.\d{1,3}$/', $found['number']) => [(float) $found['number'], strlen($found['number'])],
@@ -307,10 +305,6 @@ private static function parseString(string $httpValue): array
307305
continue;
308306
}
309307

310-
if ('' === $httpValue) {
311-
throw new SyntaxError("Invalid end of string in the HTTP textual representation of a string `$httpValue`.");
312-
}
313-
314308
$char = $httpValue[0];
315309
$offset += 1;
316310
$httpValue = substr($httpValue, 1);

0 commit comments

Comments
 (0)