Skip to content

Commit

Permalink
Re-introducing the DataType enum
Browse files Browse the repository at this point in the history
  • Loading branch information
nyamsprod committed Dec 23, 2023
1 parent df70dde commit a22bf30
Show file tree
Hide file tree
Showing 5 changed files with 168 additions and 53 deletions.
113 changes: 93 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@ build and update HTTP Structured Fields in PHP according to the [RFC8941](https:
Once installed you will be able to do the following:

```php
use Bakame\Http\StructuredFields\InnerList;
use Bakame\Http\StructuredFields\OuterList;
use Bakame\Http\StructuredFields\Token;

//1 - parsing an Accept Header
Expand All @@ -30,15 +28,15 @@ echo http_build_structured_field('list', [
[
['foo', 'bar'],
[
'expire' => $expire,
'path' => '/',
'max-age' => 2500,
'secure' => true,
'httponly' => true,
'samesite' => Token::fromString('lax'),
['expire', $expire],
['path', '/'],
[ 'max-age', 2500],
['secure', true],
['httponly', true],
['samesite', Token::fromString('lax')],
]
]
]);
],
]),
// returns ("foo" "bar");expire=@1681504328;path="/";max-age=2500;secure;httponly=?0;samesite=lax
```

Expand All @@ -65,7 +63,53 @@ header. Content validation is out of scope for this library.

### Parsing and Serializing Structured Fields

#### Basic usage
#### Functional API

> [!NOTE]
> New in version 1.2.0
Two (2) functions are added to allow parsing and building http structured fields.
`http_parse_structured_field` and `http_build_structured_field` both take as
their first argument the type of data type you either want to parse or build
and as a second parameter the data that needs to be converted.
In case of the `http_parse_structured_field`, the second value is either a string
or a stringable object and on success it will return a
`Bakame\Http\StruncturedFields\StruncturedField` implementing object otherwise an
exception will be thrown.

```php
$headerLine = 'bar;baz=42'; //the raw header line is a structured field item
$field = http_parse_structured_field('item', $headerLine);
$field->value(); // returns Token::fromString('bar); the found token value
$field->parameter('baz'); // returns 42; the value of the parameter or null if the parameter is not defined.
```

On the other hand, `http_build_structured_field` expects an iterable structure composed
of pair values that matches each data type and returns the structured field text representation
of the header.

```php
use Bakame\Http\StructuredFields\Item;
use Bakame\Http\StructuredFields\DataType;

$expire = Item::fromDateString('+30 minutes');
$data = [
[
'dumela lefatshe',
[['a', false]]
],
[
['a', 'b', $expire],
[['a', true]]
],
];
echo http_build_structured_field(DataType::List, $data);
// display "dumela lefatshe";a=?0, ("a" "b" @1703319068);a
```

The data type can be given as a string or using the `DataType` enum.

#### OOP usage

Parsing the header value is done via the `fromHttpValue` named constructor.
The method is attached to each library's structured fields representation
Expand All @@ -74,13 +118,15 @@ as shown below:
```php
declare(strict_types=1);

use Bakame\Http\StructuredFields\DataType;

require 'vendor/autoload.php';

// the raw HTTP field value is given by your application
// via any given framework, package or super global.

$headerLine = 'bar;baz=42'; //the raw header line is a structured field item
$field = parse($headerLine, 'item');
$field = DataType::Item->parse($headerLine);
$field->value(); // returns Token::fromString('bar); the found token value
$field->parameter('baz'); // returns 42; the value of the parameter or null if the parameter is not defined.
```
Expand All @@ -91,10 +137,7 @@ compliant HTTP field string value. To ease integration, the `__toString` method
implemented as an alias to the `toHttpValue` method.

````php
use function Bakame\Http\StructuredFields\http_sf_parse;
use function Bakame\Http\StructuredFields\http_sf_build;

$field = http_sf_parse('bar; baz=42; secure=?1', 'item');
$field = DataType::Item->parse('bar; baz=42; secure=?1');
echo $field->toHttpValue(); // return 'bar;baz=42;secure'
// on serialization the field has been normalized

Expand All @@ -104,8 +147,6 @@ echo $field->toHttpValue(); // return 'bar;baz=42;secure'
header('foo: '. $field->toHttpValue());
//or
header('foo: '. $field);
//or
header('foo: '. http_sf_build($field));
````

All five (5) structured data type as defined in the RFC are provided inside the
Expand Down Expand Up @@ -211,9 +252,10 @@ To ease validation a `Type::equals` method is exposed to check if the `Item` has
the expected type. It can also be used to compare types.

```php
use Bakame\Http\StructuredFields\DataType;
use Bakame\Http\StructuredFields\Type;

$field = Item::fromHttpValue('"foo"');
$field = DataType::Item->parse('"foo"');
Type::Date->equals($field); // returns false
Type::String->equals($field); // returns true;
Type::Boolean->equals(Type::String); // returns false
Expand Down Expand Up @@ -302,7 +344,7 @@ if you try to use them on any container object:
```php
use Bakame\Http\StructuredFields\Parameters;

$value = Parameters::fromHttpValue(';a=foobar']);
$value = Parameters::fromHttpValue(';a=foobar');
$value->has('b'); // return false
$value['a']->value(); // return 'foobar'
$value['b']; // triggers a InvalidOffset exception, the index does not exist
Expand Down Expand Up @@ -613,6 +655,37 @@ echo $list->toHttpValue(); //'(:SGVsbG8gV29ybGQ=: 42.0 42)'
echo $list; //'(:SGVsbG8gV29ybGQ=: 42.0 42)'
```

> [!NOTE]
> New in version 1.2.0
It is also possible to create an `OuterList` based on an iterable structure
of pairs.

```php
use Bakame\Http\StructuredFields\OuterList;

$list = OuterList::fromPairs([
[
['foo', 'bar'],
[
['expire', $expire],
['path', '/'],
[ 'max-age', 2500],
['secure', true],
['httponly', true],
['samesite', Token::fromString('lax')],
]
],
[
'coucoulesamis',
[['a', false]],
]
]);
```

The pairs definitions are the same as for creating either a `InnerList` or an `Item` using
their respective `fromPair` method.

#### Adding and updating parameters

To ease working with instances that have a `Parameters` object attached to, the following
Expand Down
2 changes: 2 additions & 0 deletions phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ parameters:
ignoreErrors:
- message: '#it_fails_to_create_an_item_from_an_array_of_pairs\(\)#'
path: tests/ItemTest.php
- message: '#Method Bakame\\Http\\StructuredFields\\DataType::build\(\) has parameter \$data with no value type specified in iterable type iterable.#'
path: src/DataType.php
- message: '#Function http_build_structured_field\(\) has parameter \$data with no value type specified in iterable type iterable.#'
path: src/functions.php
reportUnmatchedIgnoredErrors: true
44 changes: 44 additions & 0 deletions src/DataType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php

declare(strict_types=1);

namespace Bakame\Http\StructuredFields;

use Stringable;

enum DataType: string
{
case Item = 'item';
case Parameters = 'parameters';
case InnerList = 'innerlist';
case List = 'list';
case Dictionary = 'dictionary';

/**
* @throws StructuredFieldError
*/
public function parse(Stringable|string $httpValue): StructuredField
{
return match ($this) {
self::Dictionary => Dictionary::fromHttpValue($httpValue),
self::Parameters => Parameters::fromHttpValue($httpValue),
self::List => OuterList::fromHttpValue($httpValue),
self::InnerList => InnerList::fromHttpValue($httpValue),
self::Item => Item::fromHttpValue($httpValue),
};
}

/**
* @throws StructuredFieldError
*/
public function build(iterable $data): string
{
return match ($this) {
self::Dictionary => Dictionary::fromPairs($data)->toHttpValue(),
self::Parameters => Parameters::fromPairs($data)->toHttpValue(),
self::List => OuterList::fromPairs($data)->toHttpValue(),
self::InnerList => InnerList::fromPair([...$data])->toHttpValue(), /* @phpstan-ignore-line */
self::Item => Item::fromPair([...$data])->toHttpValue(), /* @phpstan-ignore-line */
};
}
}
4 changes: 2 additions & 2 deletions src/Type.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,9 @@ public static function tryFromValue(mixed $value): self|null
is_float($value) => Type::Decimal,
is_bool($value) => Type::Boolean,
is_string($value) => match (true) {
1 === preg_match('/[^\x20-\x7f]/', $value) => Type::DisplayString,
1 === preg_match("/^([a-z*][a-z\d:\/!#\$%&'*+\-.^_`|~]*)$/i", $value) => Type::Token,
null !== Token::tryFromString($value) => Type::Token,
null !== ByteSequence::tryFromEncoded($value) => Type::ByteSequence,
1 === preg_match('/[^\x20-\x7f]/', $value) => Type::DisplayString,
default => Type::String,
},
default => null,
Expand Down
58 changes: 27 additions & 31 deletions src/functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,58 +2,54 @@

declare(strict_types=1);

use Bakame\Http\StructuredFields\Dictionary;
use Bakame\Http\StructuredFields\InnerList;
use Bakame\Http\StructuredFields\Item;
use Bakame\Http\StructuredFields\OuterList;
use Bakame\Http\StructuredFields\Parameters;
use Bakame\Http\StructuredFields\DataType;
use Bakame\Http\StructuredFields\StructuredField;
use Bakame\Http\StructuredFields\StructuredFieldError;

if (!function_exists('http_parse_structured_field')) {
/**
* Parse a header conform to the HTTP Structured Field RFCs.
*
* @param 'dictionary'|'parameters'|'list'|'innerlist'|'item' $type
* @throws OutOfBoundsException If the type is unknown or unsupported
* @throws StructuredFieldError If parsing the structured field fails
*
* @throws OutOfRangeException If the value is unknown or undefined
* @see DataType::parse()
*/
function http_parse_structured_field(string $type, string $httpValue): StructuredField
function http_parse_structured_field(DataType|string $dataType, string $httpValue): StructuredField
{
return match ($type) {
'dictionary' => Dictionary::fromHttpValue($httpValue),
'parameters' => Parameters::fromHttpValue($httpValue),
'list' => OuterList::fromHttpValue($httpValue),
'innerlist' => InnerList::fromHttpValue($httpValue),
'item' => Item::fromHttpValue($httpValue), /* @phpstan-ignore-line */
default => throw new OutOfBoundsException('The submitted type "'.$type.'" is unknown or not supported,'), /* @phpstan-ignore-line */
};
if (!$dataType instanceof DataType) {
$dataType = DataType::tryFrom($dataType);
}

if (null === $dataType) {
throw new OutOfBoundsException('The submitted type "'.$dataType.'" is unknown or not supported,');
}

return $dataType->parse($httpValue);
}
}

if (!function_exists('http_build_structured_field')) {
/**
* Build an HTTP Structured Field Text representation fron an iterable PHP structure.
*
* @param 'dictionary'|'parameters'|'list'|'innerlist'|'item' $type
* @param iterable $data the iterable data used to generate the structured field
*
* @throws OutOfBoundsException If the type is unknown or unsupported
* @throws StructuredFieldError If building the structured field fails
*
* @see Dictionary::fromPairs()
* @see Parameters::fromPairs()
* @see OuterList::fromPairs()
* @see InnerList::fromPair()
* @see Item::fromPair()
* @see DataType::build()
*/
function http_build_structured_field(string $type, iterable $data): string /* @phptan-ignore-line */
function http_build_structured_field(DataType|string $dataType, iterable $data): string /* @phptan-ignore-line */
{
return match ($type) {
'dictionary' => Dictionary::fromPairs($data)->toHttpValue(),
'parameters' => Parameters::fromPairs($data)->toHttpValue(),
'list' => OuterList::fromPairs($data)->toHttpValue(),
'innerlist' => InnerList::fromPair([...$data])->toHttpValue(), /* @phpstan-ignore-line */
'item' => Item::fromPair([...$data])->toHttpValue(), /* @phpstan-ignore-line */
default => throw new OutOfBoundsException('The submitted type "'.$type.'" is unknown or not supported,'), /* @phpstan-ignore-line */
};
if (!$dataType instanceof DataType) {
$dataType = DataType::tryFrom($dataType);
}

if (null === $dataType) {
throw new OutOfBoundsException('The submitted type "'.$dataType.'" is unknown or not supported,');
}

return $dataType->build([...$data]);
}
}

0 comments on commit a22bf30

Please sign in to comment.