Skip to content

Commit 412323d

Browse files
committed
Improce ParameterAccess interface
1 parent 3fb07f8 commit 412323d

17 files changed

+170
-269
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ All Notable changes to `bakame/http-strucured-fields` will be documented in this
1313
- `Token::value` is a readonly property.
1414
- `Item::value` method returns the decoded value of an Item (returns value can be `float|int|string|bool`).
1515
- `Item::fromToken`, `Item::fromDecodedByteSequence` , `Item::fromEncodedByteSequence` to ease `Item` creation.
16+
- `Item::withValue` to ease `Item` value update.
1617
- `Parser` methods also accepts `Stringable` objects.
1718

1819
### Fixed
@@ -35,6 +36,9 @@ All Notable changes to `bakame/http-strucured-fields` will be documented in this
3536
- **[BC Break]** `isEmpty` method is removed use `hasMembers` method instead.
3637
- **[BC Break]** `Parameters::value` use `Item::value` method instead.
3738
- **[BC Break]** `Parameters::values` use `Parameters::getIterator` instead.
39+
- **[BC Break]** `Item::value` public readonly property use `Item::value` method instead.
40+
- **[BC Break]** `Item::parameters` public readonly property use `Item::parameters` method instead.
41+
- **[BC Break]** `InnerList::parameters` public readonly property use `InnerList::parameters` method instead.
3842

3943
## [0.5.0] - 2022-05-13
4044

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ use Bakame\Http\StructuredFields;
2525
$field = StructuredFields\Item::from("/terms", ['rel' => 'copyright', 'anchor' => '#foo']);
2626
echo $field->toHttpValue(); // display "/terms";rel="copyright";anchor="#foo"
2727
echo $field->value(); // display "/terms"
28-
echo $field->parameters['rel']->value(); // display "copyright"
28+
echo $field->parameters()['rel']->value(); // display "copyright"
2929
```
3030

3131
System Requirements

docs/item.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ $item = StructuredFields\Item::from("hello world", ["a" => true]);
6969
$item->value(); // returns "hello world"
7070
$item->isString(); // returns true
7171
$item->isToken(); // returns false
72-
$item->parameters["a"]->value(); //returns true
72+
$item->parameters()['a']->value(); //returns true
7373
```
7474

7575
Instantiation via type recognition is done using the `Item::from` named constructor.
@@ -84,7 +84,7 @@ It is possible to use
8484
- `Item::fromEncodedByteSequence` internally use `ByteSequence::fromEncoded`
8585
- `Item::fromDecodedByteSequence` internally use `ByteSequence::fromDecoded`
8686

87-
Those listed named constructores expect a string or a stringable object as its first argument and the
87+
Those listed named constructors expect a string or a stringable object as its first argument and the
8888
same argument definition as in `Item::from` for parameters argument.
8989

9090
```php
@@ -97,8 +97,8 @@ $item = StructuredFields\Item::fromPair([
9797
]);
9898
$item->value(); // returns "hello world"
9999
$item->isString(); // returns true
100-
$item->parameters["a"]->isByteSequence(); // returns true
101-
$item->parameters["a"]->value(); // returns the decoded value 'Hello World'
100+
$item->parameters()["a"]->isByteSequence(); // returns true
101+
$item->parameters()["a"]->value(); // returns the decoded value 'Hello World'
102102
echo $item->toHttpValue(); // returns "hello world";a=:SGVsbG8gV29ybGQ=:
103103
```
104104

@@ -112,7 +112,7 @@ a tuple composed by an array as a list where:
112112
Once instantiated, accessing `Item` properties is done via:
113113

114114
- the method `Item::value` which returns the instance underlying value fully decoded;
115-
- the readonly property `Item::parameters` which returns the parameters associated to the `Item` as a distinct `Parameters` object
115+
- the method `Item::parameters` which returns the parameters associated to the `Item` as a distinct `Parameters` object
116116

117117
```php
118118
use Bakame\Http\StructuredFields;

src/ByteSequence.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public static function fromEncoded(Stringable|string $encodedValue): self
2828

2929
$decoded = base64_decode($encodedValue, true);
3030
if (false === $decoded) {
31-
throw new SyntaxError('Invalid character in byte sequence');
31+
throw new SyntaxError('Unable to base64 decode the byte sequence');
3232
}
3333

3434
return new self($decoded);

src/ByteSequenceTest.php

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,21 @@ final class ByteSequenceTest extends StructuredFieldTest
1515
];
1616

1717
/** @test */
18-
public function it_will_fail_on_invalid_decoded_string(): void
18+
public function it_will_fail_on_invalid_decoded_string_with_inner_space(): void
1919
{
2020
$this->expectException(SyntaxError::class);
2121

2222
ByteSequence::fromEncoded('a a');
2323
}
2424

25+
/** @test */
26+
public function it_will_fail_on_invalid_decoded_string(): void
27+
{
28+
$this->expectException(SyntaxError::class);
29+
30+
ByteSequence::fromEncoded('aaaaa');
31+
}
32+
2533
/** @test */
2634
public function it_can_decode_base64_field(): void
2735
{

src/Dictionary.php

Lines changed: 9 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -73,16 +73,18 @@ public static function fromPairs(MemberOrderedMap|iterable $pairs = []): self
7373
*/
7474
public static function fromHttpValue(Stringable|string $httpValue): self
7575
{
76-
return self::fromAssociative(array_map(
77-
fn (mixed $value): mixed => is_array($value) ? InnerList::fromList(...$value) : $value,
78-
Parser::parseDictionary($httpValue)
79-
));
76+
$instance = new self();
77+
foreach (Parser::parseDictionary($httpValue) as $key => $value) {
78+
$instance->set($key, is_array($value) ? InnerList::fromList(...$value) : $value);
79+
}
80+
81+
return $instance;
8082
}
8183

8284
public function toHttpValue(): string
8385
{
8486
$formatter = fn (Item|InnerList $member, string $key): string => match (true) {
85-
$member instanceof Item && true === $member->value() => $key.$member->parameters->toHttpValue(),
87+
$member instanceof Item && true === $member->value() => $key.$member->parameters()->toHttpValue(),
8688
default => $key.'='.$member->toHttpValue(),
8789
};
8890

@@ -149,7 +151,7 @@ public function get(string|int $offset): Item|InnerList
149151
throw InvalidOffset::dueToKeyNotFound($offset);
150152
}
151153

152-
return self::filterForbiddenState($this->members[$offset]);
154+
return $this->members[$offset];
153155
}
154156

155157
/**
@@ -218,31 +220,12 @@ public function set(string $key, StructuredField|ByteSequence|Token|bool|int|flo
218220
private static function filterMember(StructuredField|ByteSequence|Token|bool|int|float|string $member): InnerList|Item
219221
{
220222
return match (true) {
221-
$member instanceof InnerList, $member instanceof Item => self::filterForbiddenState($member),
223+
$member instanceof InnerList, $member instanceof Item => $member,
222224
$member instanceof StructuredField => throw new InvalidArgument('Expecting a "'.Item::class.'" or a "'.InnerList::class.'" instance; received a "'.$member::class.'" instead.'),
223225
default => Item::from($member),
224226
};
225227
}
226228

227-
private static function filterForbiddenState(InnerList|Item $member): InnerList|Item
228-
{
229-
foreach ($member->parameters as $offset => $item) {
230-
if ($item->parameters->hasMembers()) {
231-
throw new ForbiddenStateError('Parameter member "'.$offset.'" is in invalid state; Parameters instances can only contain bare items.');
232-
}
233-
}
234-
235-
if ($member instanceof Item) {
236-
return $member;
237-
}
238-
239-
foreach ($member as $item) {
240-
self::filterForbiddenState($item);
241-
}
242-
243-
return $member;
244-
}
245-
246229
/**
247230
* Deletes members associated with the list of submitted keys.
248231
*/

src/DictionaryTest.php

Lines changed: 0 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -215,22 +215,6 @@ public function it_can_handle_string_with_comma(): void
215215
self::assertCount(2, $instance);
216216
}
217217

218-
/** @test */
219-
public function it_fails_http_conversion_with_invalid_parameters(): void
220-
{
221-
$this->expectException(StructuredFieldError::class);
222-
223-
$structuredField = Dictionary::fromPairs();
224-
$structuredField->append('item', 42);
225-
$item = $structuredField->get('item');
226-
$item->parameters->append('forty-two', '42');
227-
$wrongUpdatedItem = $item->parameters->get('forty-two');
228-
$wrongUpdatedItem->parameters->append('invalid-value', 'not-valid');
229-
self::assertCount(1, $wrongUpdatedItem->parameters);
230-
231-
$structuredField->toHttpValue();
232-
}
233-
234218
/** @test */
235219
public function it_fails_to_add_an_integer_via_array_access(): void
236220
{
@@ -276,20 +260,4 @@ public function it_can_access_the_item_value(): void
276260
self::assertInstanceOf(Item::class, $structuredField->get('false'));
277261
self::assertFalse($structuredField->get('false')->value());
278262
}
279-
280-
/** @test */
281-
public function it_will_strip_invalid_state_object_via_values_methods(): void
282-
{
283-
$this->expectException(StructuredFieldError::class);
284-
$bar = Item::from(Token::fromString('bar'));
285-
$bar->parameters->set('baz', 42);
286-
$innerList = InnerList::from('foobar');
287-
$structuredField = Dictionary::fromAssociative()
288-
->set('b', false)
289-
->set('a', $bar)
290-
->set('c', $innerList);
291-
292-
$structuredField->get('a')->parameters['baz']->parameters->set('error', 'error');
293-
$structuredField->toHttpValue();
294-
}
295263
}

src/InnerList.php

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -46,26 +46,32 @@ public static function fromList(iterable $members = [], iterable $parameters = [
4646
private static function filterMember(StructuredField|ByteSequence|Token|bool|int|float|string $member): Item
4747
{
4848
return match (true) {
49-
$member instanceof Item => self::filterForbiddenState($member),
49+
$member instanceof Item => $member,
5050
$member instanceof StructuredField => throw new InvalidArgument('Expecting a "'.Item::class.'" instance; received a "'.$member::class.'" instance instead.'),
5151
default => Item::from($member),
5252
};
5353
}
5454

55-
private static function filterForbiddenState(Item $member): Item
55+
public static function fromHttpValue(Stringable|string $httpValue): self
5656
{
57-
foreach ($member->parameters as $offset => $item) {
58-
if ($item->parameters->hasMembers()) {
59-
throw new ForbiddenStateError('Parameter member "'.$offset.'" is in invalid state; Parameters instances can only contain bare items.');
60-
}
61-
}
57+
return InnerList::fromList(...Parser::parseInnerList($httpValue));
58+
}
6259

63-
return $member;
60+
public function parameters(): Parameters
61+
{
62+
return clone $this->parameters;
6463
}
6564

66-
public static function fromHttpValue(Stringable|string $httpValue): self
65+
public function withParameters(Parameters $parameters): static
6766
{
68-
return InnerList::fromList(...Parser::parseInnerList($httpValue));
67+
if ($this->parameters->toHttpValue() === $parameters->toHttpValue()) {
68+
return $this;
69+
}
70+
71+
$newInstance = new self($parameters);
72+
$newInstance->members = $this->members;
73+
74+
return $newInstance;
6975
}
7076

7177
public function toHttpValue(): string
@@ -121,7 +127,7 @@ public function get(string|int $offset): Item
121127
throw InvalidOffset::dueToIndexNotFound($offset);
122128
}
123129

124-
return self::filterForbiddenState($this->members[$index]);
130+
return $this->members[$index];
125131
}
126132

127133
/**

src/InnerListTest.php

Lines changed: 8 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ public function it_successfully_parse_a_http_field(): void
130130
self::assertSame('hello)world', $instance->get(0)->value());
131131
self::assertSame(42, $instance->get(1)->value());
132132
self::assertSame(42.0, $instance->get(2)->value());
133-
self::assertSame('doe', $instance->get(2)->parameters['john']->value());
133+
self::assertSame('doe', $instance->get(2)->parameters()['john']->value());
134134
}
135135

136136
/** @test */
@@ -177,21 +177,6 @@ public function testArrayAccessThrowsInvalidIndex2(): void
177177
self::assertCount(0, $sequence);
178178
}
179179

180-
/** @test */
181-
public function it_fails_http_conversion_with_invalid_parameters(): void
182-
{
183-
$this->expectException(StructuredFieldError::class);
184-
185-
$structuredField = InnerList::from(69);
186-
$item = $structuredField->get(0);
187-
$item->parameters->append('forty-two', '42');
188-
$wrongUpdatedItem = $item->parameters->get('forty-two');
189-
$wrongUpdatedItem->parameters->append('invalid-value', 'not-valid');
190-
self::assertCount(1, $wrongUpdatedItem->parameters);
191-
192-
$structuredField->toHttpValue();
193-
}
194-
195180
/** @test */
196181
public function it_fails_to_fetch_an_value_using_an_integer(): void
197182
{
@@ -213,14 +198,14 @@ public function it_can_access_the_item_value(): void
213198
}
214199

215200
/** @test */
216-
public function it_will_strip_invalid_state_object_via_values_methods(): void
201+
public function it_can_create_via_with_parameters_method_a_new_object(): void
217202
{
218-
$this->expectException(ForbiddenStateError::class);
219-
$bar = Item::from(Token::fromString('bar'));
220-
$bar->parameters->set('baz', 42);
221-
$structuredField = InnerList::from(false, $bar);
222-
$structuredField[1]->parameters['baz']->parameters->set('error', 'error');
203+
$instance1 = InnerList::fromList([Token::fromString('babayaga'), 'a', true], ['a' => true]);
204+
$instance2 = $instance1->withParameters(Parameters::fromAssociative(['a' => true]));
205+
$instance3 = $instance1->withParameters(Parameters::fromAssociative(['a' => false]));
223206

224-
$structuredField->toHttpValue();
207+
self::assertSame($instance1, $instance2);
208+
self::assertNotSame($instance1->parameters(), $instance3->parameters());
209+
self::assertEquals(iterator_to_array($instance1), iterator_to_array($instance3));
225210
}
226211
}

0 commit comments

Comments
 (0)