Skip to content

Commit f51fad0

Browse files
committed
Adding support for the Date Type
1 parent 9c1e3eb commit f51fad0

14 files changed

+217
-92
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@ All Notable changes to `bakame/http-strucured-fields` will be documented in this
66

77
### Added
88

9-
- `ParameterAccess` interface updated with 3 new methods to ease parameter members modification.
109
- Support for `Stringable` instance added to `Item::from`, the instances will be converted to the string data type.
10+
- Support for the upcoming `Date` data type in `Item`. It is represented as a `DateTimeImmutable` object.
11+
- `ParameterAccess` interface updated with 3 new methods to ease parameter members modification.
1112
- `Parameter::create` named constructor to create a new instance without any parameter.
1213
- `Dictionnary::create` named constructor to create a new instance without any parameter.
1314

docs/item.md

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,15 @@ Items can have different types that are translated to PHP using:
1010

1111
The table below summarizes the item value type.
1212

13-
| HTTP DataType | Package Data Type | validation method |
14-
|---------------|----------------------|------------------------|
15-
| Integer | `int` | `Item::isInteger` |
16-
| Decimal | `float` | `Item::isDecimal` |
17-
| String | `string` | `Item::isString` |
18-
| Boolean | `bool` | `Item::isBoolean` |
19-
| Token | class `Token` | `Item::isToken` |
20-
| Byte Sequence | class `ByteSequence` | `Item::isByteSequence` |
13+
| HTTP DataType | Package Data Type | validation method |
14+
|---------------|---------------------------|------------------------|
15+
| Integer | `int` | `Item::isInteger` |
16+
| Decimal | `float` | `Item::isDecimal` |
17+
| String | `string` | `Item::isString` |
18+
| Boolean | `bool` | `Item::isBoolean` |
19+
| Token | class `Token` | `Item::isToken` |
20+
| Byte Sequence | class `ByteSequence` | `Item::isByteSequence` |
21+
| Date | class `DateTimeImmutable` | `Item::isDate` |
2122

2223
### Token
2324

phpstan.neon

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,4 @@ parameters:
1010
ignoreErrors:
1111
reportUnmatchedIgnoredErrors: true
1212
typeAliases:
13-
DataType: '\Bakame\Http\StructuredFields\ByteSequence|\Bakame\Http\StructuredFields\Token|\Stringable|bool|int|float|string'
13+
DataType: '\Bakame\Http\StructuredFields\ByteSequence|\Bakame\Http\StructuredFields\Token|\DateTimeInterface|\Stringable|bool|int|float|string'

src/Dictionary.php

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace Bakame\Http\StructuredFields;
66

7+
use DateTimeInterface;
78
use Iterator;
89
use Stringable;
910
use function array_key_exists;
@@ -218,14 +219,14 @@ public function pair(int $index): array
218219
*
219220
* @throws SyntaxError If the string key is not a valid
220221
*/
221-
public function set(string $key, StructuredField|ByteSequence|Token|Stringable|bool|int|float|string $member): self
222+
public function set(string $key, StructuredField|ByteSequence|Token|DateTimeInterface|Stringable|bool|int|float|string $member): self
222223
{
223224
$this->members[MapKey::fromString($key)->value] = self::filterMember($member);
224225

225226
return $this;
226227
}
227228

228-
private static function filterMember(StructuredField|ByteSequence|Token|Stringable|bool|int|float|string $member): InnerList|Item
229+
private static function filterMember(StructuredField|ByteSequence|Token|DateTimeInterface|Stringable|bool|int|float|string $member): InnerList|Item
229230
{
230231
return match (true) {
231232
$member instanceof InnerList, $member instanceof Item => $member,
@@ -258,7 +259,7 @@ public function clear(): self
258259
*
259260
* @throws SyntaxError If the string key is not a valid
260261
*/
261-
public function append(string $key, StructuredField|ByteSequence|Token|bool|int|float|string $member): self
262+
public function append(string $key, StructuredField|ByteSequence|DateTimeInterface|Token|bool|int|float|string $member): self
262263
{
263264
unset($this->members[$key]);
264265

@@ -272,7 +273,7 @@ public function append(string $key, StructuredField|ByteSequence|Token|bool|int|
272273
*
273274
* @throws SyntaxError If the string key is not a valid
274275
*/
275-
public function prepend(string $key, StructuredField|ByteSequence|Token|bool|int|float|string $member): self
276+
public function prepend(string $key, StructuredField|ByteSequence|DateTimeInterface|Token|bool|int|float|string $member): self
276277
{
277278
unset($this->members[$key]);
278279

@@ -284,7 +285,7 @@ public function prepend(string $key, StructuredField|ByteSequence|Token|bool|int
284285
/**
285286
* Merges multiple instances using iterable associative structures.
286287
*
287-
* @param iterable<string, InnerList<int, Item>|Item|ByteSequence|Token|bool|int|float|string> ...$others
288+
* @param iterable<string, InnerList<int, Item>|Item|ByteSequence|DateTimeInterface|Token|bool|int|float|string> ...$others
288289
*/
289290
public function mergeAssociative(iterable ...$others): self
290291
{

src/InnerList.php

Lines changed: 13 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace Bakame\Http\StructuredFields;
66

7+
use DateTimeInterface;
78
use Iterator;
89
use Stringable;
910
use function array_filter;
@@ -27,7 +28,7 @@ private function __construct(private readonly Parameters $parameters)
2728
/**
2829
* Returns a new instance.
2930
*/
30-
public static function from(Item|ByteSequence|Token|Stringable|bool|int|float|string ...$members): self
31+
public static function from(Item|ByteSequence|Token|DateTimeInterface|Stringable|bool|int|float|string ...$members): self
3132
{
3233
return self::fromList($members);
3334
}
@@ -46,7 +47,7 @@ public static function fromList(iterable $members, iterable $parameters = []): s
4647
return $instance;
4748
}
4849

49-
private static function filterMember(StructuredField|ByteSequence|Token|Stringable|bool|int|float|string $member): Item
50+
private static function filterMember(StructuredField|ByteSequence|Token|DateTimeInterface|Stringable|bool|int|float|string $member): Item
5051
{
5152
return match (true) {
5253
$member instanceof Item => $member,
@@ -65,29 +66,19 @@ public function parameters(): Parameters
6566
return clone $this->parameters;
6667
}
6768

68-
public function prependParameter(string $name, Item|ByteSequence|Token|Stringable|bool|int|float|string $member): static
69+
public function prependParameter(string $name, Item|ByteSequence|Token|DateTimeInterface|Stringable|bool|int|float|string $member): static
6970
{
70-
$parameters = clone $this->parameters;
71-
72-
return $this->withParameters($parameters->prepend($name, $member));
71+
return $this->withParameters($this->parameters()->prepend($name, $member));
7372
}
7473

75-
public function appendParameter(string $name, Item|ByteSequence|Token|Stringable|bool|int|float|string $member): static
74+
public function appendParameter(string $name, Item|ByteSequence|Token|DateTimeInterface|Stringable|bool|int|float|string $member): static
7675
{
77-
$parameters = clone $this->parameters;
78-
79-
return $this->withParameters($parameters->set($name, $member));
76+
return $this->withParameters($this->parameters()->append($name, $member));
8077
}
8178

82-
public function withoutParameter(string $name): static
79+
public function withoutParameter(string ...$name): static
8380
{
84-
if (!$this->parameters->has($name)) {
85-
return $this;
86-
}
87-
88-
$parameters = clone $this->parameters;
89-
90-
return $this->withParameters($parameters->delete($name));
81+
return $this->withParameters($this->parameters()->delete(...$name));
9182
}
9283

9384
public function withParameters(Parameters $parameters): static
@@ -161,7 +152,7 @@ public function get(string|int $offset): Item
161152
/**
162153
* Insert members at the beginning of the list.
163154
*/
164-
public function unshift(StructuredField|ByteSequence|Token|Stringable|bool|int|float|string ...$members): self
155+
public function unshift(StructuredField|ByteSequence|Token|DateTimeInterface|Stringable|bool|int|float|string ...$members): self
165156
{
166157
$this->members = [...array_map(self::filterMember(...), array_values($members)), ...$this->members];
167158

@@ -171,7 +162,7 @@ public function unshift(StructuredField|ByteSequence|Token|Stringable|bool|int|f
171162
/**
172163
* Insert members at the end of the list.
173164
*/
174-
public function push(StructuredField|ByteSequence|Token|Stringable|bool|int|float|string ...$members): self
165+
public function push(StructuredField|ByteSequence|Token|DateTimeInterface|Stringable|bool|int|float|string ...$members): self
175166
{
176167
$this->members = [...$this->members, ...array_map(self::filterMember(...), array_values($members))];
177168

@@ -183,7 +174,7 @@ public function push(StructuredField|ByteSequence|Token|Stringable|bool|int|floa
183174
*
184175
* @throws InvalidOffset If the index does not exist
185176
*/
186-
public function insert(int $index, StructuredField|ByteSequence|Token|Stringable|bool|int|float|string ...$members): self
177+
public function insert(int $index, StructuredField|ByteSequence|Token|DateTimeInterface|Stringable|bool|int|float|string ...$members): self
187178
{
188179
$offset = $this->filterIndex($index);
189180
match (true) {
@@ -196,7 +187,7 @@ public function insert(int $index, StructuredField|ByteSequence|Token|Stringable
196187
return $this;
197188
}
198189

199-
public function replace(int $index, StructuredField|ByteSequence|Token|Stringable|bool|int|float|string $member): self
190+
public function replace(int $index, StructuredField|ByteSequence|Token|DateTimeInterface|Stringable|bool|int|float|string $member): self
200191
{
201192
if (null === ($offset = $this->filterIndex($index))) {
202193
throw InvalidOffset::dueToIndexNotFound($index);

src/Item.php

Lines changed: 42 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44

55
namespace Bakame\Http\StructuredFields;
66

7+
use DateTimeImmutable;
8+
use DateTimeInterface;
9+
use DateTimeZone;
710
use Stringable;
811
use function count;
912
use function in_array;
@@ -23,7 +26,7 @@
2326
final class Item implements StructuredField, ParameterAccess
2427
{
2528
private function __construct(
26-
private readonly Token|ByteSequence|int|float|string|bool $value,
29+
private readonly Token|ByteSequence|DateTimeImmutable|int|float|string|bool $value,
2730
private readonly Parameters $parameters
2831
) {
2932
}
@@ -54,7 +57,7 @@ public static function fromPair(array $pair): self
5457
* @param iterable<string,Item|DataType> $parameters
5558
*/
5659
public static function from(
57-
Token|ByteSequence|Stringable|int|float|string|bool $value,
60+
Token|ByteSequence|Stringable|DateTimeInterface|int|float|string|bool $value,
5861
iterable $parameters = []
5962
): self {
6063
return new self(self::filterValue($value), Parameters::fromAssociative($parameters));
@@ -149,6 +152,7 @@ public static function fromHttpValue(Stringable|string $httpValue): self
149152
'"' === $itemString[0] => self::parseString($itemString),
150153
':' === $itemString[0] => self::parseBytesSequence($itemString),
151154
'?' === $itemString[0] => self::parseBoolean($itemString),
155+
'@' === $itemString[0] => self::parseDate($itemString),
152156
1 === preg_match('/^(-?\d)/', $itemString) => self::parseNumber($itemString),
153157
1 === preg_match('/^([a-z*])/i', $itemString) => self::parseToken($itemString),
154158
default => throw new SyntaxError('The HTTP textual representation "'.$httpValue.'" for an item is unknown or unsupported.'),
@@ -232,6 +236,24 @@ private static function parseNumber(string $string): array
232236
return [$number, substr($string, strlen($found['number']))];
233237
}
234238

239+
/**
240+
* Parses an HTTP textual representation of an Item as a Data Type number.
241+
*
242+
* @return array{0:DateTimeImmutable, 1:string}
243+
*/
244+
private static function parseDate(string $string): array
245+
{
246+
[$timestamp, $parameters] = self::parseNumber(substr($string, 1));
247+
if (!is_int($timestamp)) {
248+
throw new SyntaxError("The HTTP textual representation \"$string\" for a date contains invalid characters.");
249+
}
250+
251+
return [
252+
(new DateTimeImmutable('NOW', new DateTimeZone('UTC')))->setTimestamp($timestamp),
253+
$parameters,
254+
];
255+
}
256+
235257
/**
236258
* Parses an HTTP textual representation of an Item as a String Data Type.
237259
*
@@ -291,17 +313,18 @@ private function serializeDecimal(float $value): string
291313
/**
292314
* Returns the underlying value decoded.
293315
*/
294-
public function value(): Token|ByteSequence|int|float|string|bool
316+
public function value(): Token|ByteSequence|DateTimeImmutable|int|float|string|bool
295317
{
296318
return $this->value;
297319
}
298320

299-
public function withValue(Token|ByteSequence|Stringable|int|float|string|bool $value): self
321+
public function withValue(Token|ByteSequence|DateTimeInterface|Stringable|int|float|string|bool $value): self
300322
{
301323
$newValue = self::filterValue($value);
302324

303325
if (
304326
($newValue === $this->value)
327+
|| ($newValue instanceof DateTimeImmutable && $this->value instanceof DateTimeImmutable && $this->value == $newValue)
305328
|| ($newValue instanceof Token && $this->value instanceof Token && $this->value->value === $newValue->value)
306329
|| ($newValue instanceof ByteSequence && $this->value instanceof ByteSequence && $this->value->encoded() === $newValue->encoded())
307330
) {
@@ -316,29 +339,19 @@ public function parameters(): Parameters
316339
return clone $this->parameters;
317340
}
318341

319-
public function prependParameter(string $name, Item|ByteSequence|Token|bool|int|float|string $member): static
342+
public function prependParameter(string $name, Item|ByteSequence|Token|DateTimeInterface|Stringable|bool|int|float|string $member): static
320343
{
321-
$parameters = clone $this->parameters;
322-
323-
return $this->withParameters($parameters->prepend($name, $member));
344+
return $this->withParameters($this->parameters()->prepend($name, $member));
324345
}
325346

326-
public function appendParameter(string $name, Item|ByteSequence|Token|Stringable|bool|int|float|string $member): static
347+
public function appendParameter(string $name, Item|ByteSequence|Token|DateTimeInterface|Stringable|bool|int|float|string $member): static
327348
{
328-
$parameters = clone $this->parameters;
329-
330-
return $this->withParameters($parameters->append($name, $member));
349+
return $this->withParameters($this->parameters()->append($name, $member));
331350
}
332351

333-
public function withoutParameter(string $name): static
352+
public function withoutParameter(string ...$name): static
334353
{
335-
if (!$this->parameters->has($name)) {
336-
return $this;
337-
}
338-
339-
$parameters = clone $this->parameters;
340-
341-
return $this->withParameters($parameters->delete($name));
354+
return $this->withParameters($this->parameters()->delete(...$name));
342355
}
343356

344357
public function withParameters(Parameters $parameters): static
@@ -363,6 +376,7 @@ public function toHttpValue(): string
363376
is_float($this->value) => $this->serializeDecimal($this->value),
364377
is_bool($this->value) => '?'.($this->value ? '1' : '0'),
365378
$this->value instanceof Token => $this->value->value,
379+
$this->value instanceof DateTimeImmutable => '@'.$this->value->getTimestamp(),
366380
default => ':'.$this->value->encoded().':',
367381
}.$this->parameters->toHttpValue();
368382
}
@@ -397,12 +411,19 @@ public function isByteSequence(): bool
397411
return $this->value instanceof ByteSequence;
398412
}
399413

400-
private static function filterValue(float|bool|int|string|Token|ByteSequence|Stringable $value): ByteSequence|string|Token|int|bool|float
414+
public function isDate(): bool
415+
{
416+
return $this->value instanceof DateTimeImmutable;
417+
}
418+
419+
private static function filterValue(float|bool|int|string|Token|ByteSequence|DateTimeInterface|Stringable $value): ByteSequence|DateTimeImmutable|Token|string|int|bool|float
401420
{
402421
return match (true) {
403422
is_int($value) => self::filterInteger($value),
404423
is_float($value) => self::filterDecimal($value),
405424
is_string($value) || $value instanceof Stringable => self::filterString($value),
425+
$value instanceof DateTimeImmutable => $value,
426+
$value instanceof DateTimeInterface => DateTimeImmutable::createFromInterface($value),
406427
default => $value,
407428
};
408429
}

0 commit comments

Comments
 (0)