+While this package parses and serializes the HTTP value, it does not validate its content
+against any conformance rule. You are still required to perform a compliance check
+against the constraints of the corresponding field. Content validation is
+out of scope for this library even though you can leverage some of its feature to
+ease the required validation.
+
+
+To quickly parse or serialize one of the five (5) available data type according to the RFC, you can use the `DataType` enum.
+Apart from listing the data types (`List`, `InnerList`, `Parameters`, `Dictionary` and `Item`) you can give to
+its `parse` method a string or a stringable object representing a field text representation. On success,
+it will return an object representing the structured field otherwise an exception will be thrown.
+
+```php
+$headerLine = 'bar;baz=42'; //the raw header line is a structured field 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.
+```
+
+To complement the behaviour, you can use its `serialize` method to turn an iterable structure
+composed of pair values that matches any structured field data type and returns its
+text representation.
+
+```php
+use Bakame\Http\StructuredFields\Item;
+use Bakame\Http\StructuredFields\DataType;
+
+echo DataType::List->serialize([
+ [
+ 'dumela lefatshe',
+ [['a', false]]
+ ],
+ [
+ ['a', 'b', Item::fromDateString('+30 minutes')],
+ [['a', true]]
+ ],
+]);
+// display "dumela lefatshe";a=?0, ("a" "b" @1703319068);a
+```
+
+The `serialize` method is a shortcut to converting the iterable structure into a `StructuredField` via
+the `create` method and calling on the newly created object its `toHttpValue` method. With that
+in mind, it is possible to rewrite The last example:
+
+```php
+use Bakame\Http\StructuredFields\Item;
+use Bakame\Http\StructuredFields\DataType;
+
+$list = DataType::List->create([
+ [
+ 'dumela lefatshe',
+ [['a', false]]
+ ],
+ [
+ ['a', 'b', Item::fromDateString('+30 minutes')],
+ [['a', true]]
+ ],
+]);
+
+echo $list->toHttpValue();
+// display "dumela lefatshe";a=?0, ("a" "b" @1703319068);a
+```
+
+
+While the format can be overwhelming at first, you will come to understand it while reading
+the rest of the documentation. Under the hood, the `DataType` enum uses the mechanism discussed hereafter.
+
+
+#### Using specific data type classes
+
+The package provides specific classes for each data type. Parsing is done their respective
+`fromHttpValue` named constructor. A example of how the method works can be seen below
+using the `Item` class:
+
+```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 = Item::fromHttpValue($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.
+```
+
+
+The DataType::parse method uses the fromHttpValue named constructor for
+each specific class to generate the structured field PHP representation.
+
+
+The `fromHttpValue` method returns an instance which implements the `StructuredField` interface.
+The interface provides the `toHttpValue` method that serializes it into a normalized RFC
+compliant HTTP field string value. To ease integration, the `__toString` method is
+implemented as an alias to the `toHttpValue` method.
+
+````php
+$field = Item::fromHttpValue('bar; baz=42; secure=?1');
+echo $field->toHttpValue(); // return 'bar;baz=42;secure'
+// on serialization the field has been normalized
+
+// the HTTP response is build by your application
+// via any given framework, package or PHP native function.
+
+header('foo: '. $field->toHttpValue());
+//or
+header('foo: '. $field);
+````
+
+
+This is the mechanism used by the DataType::serialize method. Once the Structured
+field has been created, the method calls its toHttpValue method.
+
+>
+
+All five (5) structured data type as defined in the RFC are provided inside the
+`Bakame\Http\StructuredFields` namespace. They all implement the
+`StructuredField` interface and expose a `fromHttpValue` named constructor:
+
+- `Item`
+- `Parameters`
+- `Dictionary`
+- `OuterList` (named `List` in the RFC but renamed in the package because `list` is a reserved word in PHP.)
+- `InnerList`
+
+### Accessing Structured Fields Values
+
+#### RFC Value type
+
+Per the RFC, items value can have different types that are translated to PHP using:
+
+- native type or classes where possible;
+- specific classes defined in the package namespace to represent non-native type
+
+The table below summarizes the item value type.
+
+| RFC Type | PHP Type | Package Enum Name | Package Enum Value |
+|-------------------|---------------------------|-----------------------|--------------------|
+| Integer | `int` | `Type::Integer` | `ìnteger` |
+| Decimal | `float` | `Type::Decimal` | `decimal` |
+| String | `string` | `Type::String` | `string` |
+| Boolean | `bool` | `Type::Boolean` | `boolean` |
+| Token | class `Token` | `Type::Token` | `token` |
+| Byte Sequence | class `ByteSequence` | `Type::ByteSequence` | `binary` |
+| Date (*) | class `DateTimeImmutable` | `Type::Date` | `date` |
+| DisplayString (*) | class `DisplayString` | `Type::DisplayString` | `displaystring` |
+
+
+The Date and DisplayString type are not yet part of any accepted
+RFC. But they are already added as new types in the super-seeding
+RFC proposal.
+
+
+>
+> See https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-sfbis
+> for more information.
+
+The Enum `Type` list all available types and can be used to determine the RFC type
+corresponding to a PHP structure using the `Type::fromVariable` static method.
+The method will throw if the structure is not recognized. Alternatively
+it is possible to use the `Type::tryFromVariable` which will instead
+return `null` on unidentified type. On success both methods
+return the corresponding enum `Type`.
+
+```php
+use Bakame\Http\StructuredFields\Type;
+
+echo Type::fromVariable(42)->value; // returns 'integer'
+echo Type::fromVariable(42.0)->name; // returns 'Decimal'
+echo Type::fromVariable(new SplTempFileObject()); // throws InvalidArgument
+echo Type::tryFromVariable(new SplTempFileObject()); // returns null
+```
+
+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 = DataType::Item->parse('"foo"');
+Type::Date->equals($field); // returns false
+Type::String->equals($field); // returns true;
+Type::Boolean->equals(Type::String); // returns false
+```
+
+The RFC defines three (3) specific data types that can not be represented by
+PHP default type system, for them, we have defined three classes `Token`,
+`ByteSequence` and `DisplayString` to help with their representation.
+
+```php
+use Bakame\Http\StructuredFields\ByteSequence;
+use Bakame\Http\StructuredFields\DisplayString;
+use Bakame\Http\StructuredFields\Token;
+
+Token::fromString(string|Stringable $value): Token
+ByteSequence::fromDecoded(string|Stringable $value): ByteSequence;
+ByteSequence::fromEncoded(string|Stringable $value): ByteSequence;
+DisplayString::fromDecoded(string|Stringable $value): DisplayString;
+DisplayString::fromEncoded(string|Stringable $value): DisplayString;
+```
+
+All classes are final and immutable; their value can not be modified once
+instantiated. To access their value, they expose the following API:
+
+```php
+use Bakame\Http\StructuredFields\Token;
+use Bakame\Http\StructuredFields\ByteSequence;
+use Bakame\Http\StructuredFields\DisplayString;
+
+$token = Token::fromString('application/text+xml');
+echo $token->toString(); // returns 'application/text+xml'
+
+$byte = DisplayString::fromDecoded('füü');
+$byte->decoded(); // returns 'füü'
+$byte->encoded(); // returns 'f%c3%bc%c3%bc'
+
+$displayString = ByteSequence::fromDecoded('Hello world!');
+$byte->decoded(); // returns 'Hello world!'
+$byte->encoded(); // returns 'SGVsbG8gd29ybGQh'
+
+$token->equals($byte); // will return false;
+$displayString->equals($byte); // will return false;
+$byte->equals(ByteSequence::fromEncoded('SGVsbG8gd29ybGQh')); // will return true
+
+$token->type(); // returns Type::Token enum
+$byte->type(); // returns Type::ByteSequence
+$displayString->type(); // returns Type::DisplayString
+```
+
+
+The classes DO NOT expose the Stringable interface to help distinguish
+them from a string or a stringable object
+
+
+#### Item
+
+The defined types are all attached to an `Item` object where their value and
+type are accessible using the following methods:
+
+```php
+use Bakame\Http\StructuredFields\Item;
+use Bakame\Http\StructuredFields\Type;
+
+$item = Item::fromHttpValue('@1234567890');
+$item->type(); // return Type::Date;
+$item->value() // return the equivalent to DateTimeImmutable('@1234567890');
+```
+
+#### Containers
+
+All containers objects implement PHP `IteratorAggregate`, `Countable` and `ArrayAccess`
+interfaces. Their members can be accessed using the following shared methods
+
+```php
+$container->keys(): array;
+$container->has(string|int ...$offsets): bool;
+$container->get(string|int $offset): StrucuredField;
+$container->hasMembers(): bool;
+$container->hasNoMembers(): bool;
+```
+
+
+The get method will throw an InvalidOffset exception if no member exists for the given $offset.
+
+
+
+To avoid invalid states, `ArrayAccess` modifying methods throw a `ForbiddenOperation`
+if you try to use them on any container object:
+
+```php
+use Bakame\Http\StructuredFields\Parameters;
+
+$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
+$value['a'] = 23 // triggers a ForbiddenOperation exception
+unset($value['a']); // triggers a ForbiddenOperation exception
+```
+
+The `Dictionary` and `Parameters` classes also allow accessing its members as pairs:
+
+```php
+$container->hasPair(int ...$offsets): bool;
+$container->pair(int $offset): array{0:string, 1:StructuredField};
+$container->toPairs(): iterable;
+```
+
+
+The pair method will throw an InvalidOffset exception if no member exists for the given $offset.
+
+
+#### Accessing the parameters values
+
+Accessing the associated `Parameters` instance attached to an `InnerList` or a `Item` instances
+is done using the following methods:
+
+```php
+use Bakame\Http\StructuredFields\InnerList;
+use Bakame\Http\StructuredFields\Item;
+use Bakame\Http\StructuredFields\Parameters;
+
+$field->parameter(string $key): ByteSequence|Token|DisplayString|DateTimeImmutable|Stringable|string|int|float|bool|null;
+$field->parameters(): Parameters;
+$field->parameterByIndex(int $index): array{0:string, 1:ByteSequence|Token|DisplayString|DateTimeImmutable|Stringable|string|int|float|boo}
+InnerList::toPair(): array{0:list, 1:Parameters}>};
+Item::toPair(): array{0:ByteSequence|Token|DisplayString|DateTimeImmutable|Stringable|string|int|float|bool, 1:Parameters}>};
+```
+
+
+
The parameter method will return null if no value is found for the given key.
+
The parameterByIndex method is added in version 1.1.0 and returns an empty array if no parameter is found for the given index.
+
+
+### Building and Updating Structured Fields Values
+
+Every value object can be used as a builder to create an HTTP field value. Because we are
+using immutable value objects any change to the value object will return a new instance
+with the changes applied and leave the original instance unchanged.
+
+#### Items value
+
+The `Item` value object exposes the following named constructors to instantiate
+bare items (ie: item without parameters attached to them).
+
+```php
+use Bakame\Http\StructuredFields\ByteSequence;
+use Bakame\Http\StructuredFields\Item;
+use Bakame\Http\StructuredFields\Token;
+
+Item:new(DateTimeInterface|ByteSequence|Token|DisplayString|string|int|array|float|bool $value): self
+Item::fromDecodedByteSequence(Stringable|string $value): self;
+Item::fromEncodedDisplayString(Stringable|string $value): self;
+Item::fromDecodedDisplayString(Stringable|string $value): self;
+Item::fromEncodedByteSequence(Stringable|string $value): self;
+Item::fromToken(Stringable|string $value): self;
+Item::fromString(Stringable|string $value): self;
+Item::fromDate(DateTimeInterface $datetime): self;
+Item::fromDateFormat(string $format, string $datetime): self;
+Item::fromDateString(string $datetime, DateTimeZone|string|null $timezone = null): self;
+Item::fromTimestamp(int $value): self;
+Item::fromDecimal(int|float $value): self;
+Item::fromInteger(int|float $value): self;
+Item::true(): self;
+Item::false(): self;
+```
+
+To update the `Item` instance value, use the `withValue` method:
+
+```php
+use Bakame\Http\StructuredFields\Item;
+
+Item::withValue(DateTimeInterface|ByteSequence|Token|DisplayString|string|int|float|bool $value): static
+```
+
+#### Ordered Maps
+
+The `Dictionary` and `Parameters` are ordered map instances. They can be built using their keys with an associative iterable structure as shown below
+
+```php
+use Bakame\Http\StructuredFields\Dictionary;
+
+$value = Dictionary::fromAssociative([
+ 'b' => Item::false(),
+ 'a' => Item::fromToken('bar'),
+ 'c' => new DateTimeImmutable('2022-12-23 13:00:23'),
+]);
+
+echo $value->toHttpValue(); //"b=?0, a=bar, c=@1671800423"
+echo $value; //"b=?0, a=bar, c=@1671800423"
+```
+
+or using their indexes with an iterable structure of pairs (tuple) as defined in the RFC:
+
+```php
+use Bakame\Http\StructuredFields\Parameters;
+use Bakame\Http\StructuredFields\Item;
+
+$value = Parameters::fromPairs(new ArrayIterator([
+ ['b', Item::false()],
+ ['a', Item::fromToken('bar')],
+ ['c', new DateTime('2022-12-23 13:00:23')]
+]));
+
+echo $value->toHttpValue(); //;b=?0;a=bar;c=@1671800423
+echo $value; //;b=?0;a=bar;c=@1671800423
+```
+
+If the preference is to use the builder pattern, the same result can be achieved with the
+following steps:
+
+- First create a `Parameters` or a `Dictionary` instance using the `new` named constructor which
+ returns a new instance with no members.
+- And then, use any of the following modifying methods to populate it.
+
+```php
+$map->add(string $key, $value): static;
+$map->append(string $key, $value): static;
+$map->prepend(string $key, $value): static;
+$map->mergeAssociative(...$others): static;
+$map->mergePairs(...$others): static;
+$map->remove(string|int ...$key): static;
+```
+
+As shown below:
+`
+```php
+use Bakame\Http\StructuredFields\Dictionary;
+use Bakame\Http\StructuredFields\Item;
+use Bakame\Http\StructuredFields\Token;
+
+$value = Dictionary::new()
+ ->add('a', InnerList::new(
+ Item::fromToken('bar'),
+ Item::fromString('42'),
+ Item::fromInteger(42),
+ Item::fromDecimal(42)
+ ))
+ ->prepend('b', Item::false())
+ ->append('c', Item::fromDateString('2022-12-23 13:00:23'))
+;
+
+echo $value->toHttpValue(); //b=?0, a=(bar "42" 42 42.0), c=@1671800423
+echo $value; //b=?0, a=(bar "42" 42 42.0), c=@1671800423
+```
+
+Since version `1.1.0` it is possible to also build `Dictionary` and `Parameters` instances
+using indexes and pair as per described in the RFC.
+
+The `$pair` parameter is a tuple (ie: an array as list with exactly two members) where:
+
+- the first array member is the parameter `$key`
+- the second array member is the parameter `$value`
+
+```php
+// since version 1.1
+$map->unshift(array ...$pairs): static;
+$map->push(array ...$pairs): static;
+$map->insert(int $key, array ...$pairs): static;
+$map->replace(int $key, array $pair): static;
+$map->removeByKeys(string ...$keys): static;
+$map->removeByIndices(int ...$indices): static;
+```
+
+We can rewrite the previous example
+
+```php
+use Bakame\Http\StructuredFields\Dictionary;
+use Bakame\Http\StructuredFields\Item;
+use Bakame\Http\StructuredFields\Token;
+
+$value = Dictionary::new()
+ ->push(
+ ['a', InnerList::new(
+ Item::fromToken('bar'),
+ Item::fromString('42'),
+ Item::fromInteger(42),
+ Item::fromDecimal(42)
+ )],
+ ['c', Item::true()]
+ )
+ ->unshift(['b', Item::false()])
+ ->replace(2, ['c', Item::fromDateString('2022-12-23 13:00:23')])
+;
+
+echo $value->toHttpValue(); //b=?0, a=(bar "42" 42 42.0), c=@1671800423
+echo $value; //b=?0, a=(bar "42" 42 42.0), c=@1671800423
+```
+
+
on duplicate `keys` pair values are merged as per RFC logic.
+
+The `remove` always accepted string or integer as input. Since version `1.1` the method is fixed to
+remove the corresponding pair if its index is given to the method.
+
+```diff
+remove('b', 2)->toHttpValue(); // returns a=(bar "42" 42 42.0), c=@1671800423
++ echo $field->remove('b', 2)->toHttpValue(); // returns a=(bar "42" 42 42.0)
+```
+
+If a stricter approach is needed, use the following new methods `removeByIndices` and/or `removeByKeys`:
+
+```php
+use Bakame\Http\StructuredFields\Parameters;
+
+$field = Parameters::fromHttpValue(';expire=@1681504328;path="/";max-age=2500;secure;httponly=?0;samesite=lax');
+echo $field->removeByIndices(4, 2, 0)->toHttpValue(); // returns ;path="/";secure;samesite=lax
+echo $field->removeByKeys('expire', 'httponly', 'max-age')->toHttpValue(); // returns ;path="/";secure;samesite=lax
+```
+
+#### Automatic conversion
+
+For all containers, to ease instantiation the following automatic conversion are applied on
+the member argument of each modifying methods.
+
+If the submitted type is:
+
+- a `StructuredField` implementing object, it will be passed as is
+- an iterable structure, it will be converted to an `InnerList` instance using `InnerList::new`
+- otherwise, it is converted into an `Item` using the `Item::new` named constructor.
+
+If no conversion is possible an `InvalidArgument` exception will be thrown.
+
+This means that both constructs below built equal objects
+
+```php
+use Bakame\Http\StructuredFields\Dictionary;
+use Bakame\Http\StructuredFields\Item;
+use Bakame\Http\StructuredFields\Token;
+
+echo Dictionary::new()
+ ->add('a', InnerList::new(
+ Item::fromToken('bar'),
+ Item::fromString('42'),
+ Item::fromInteger(42),
+ Item::fromDecimal(42)
+ ))
+ ->prepend('b', Item::false())
+ ->append('c', Item::fromDateString('2022-12-23 13:00:23'))
+ ->toHttpValue()
+;
+
+echo Dictionary::new()
+ ->add('a', [Token::fromString('bar'), '42', 42, 42.0])
+ ->prepend('b', false)
+ ->append('c', new DateTimeImmutable('2022-12-23 13:00:23'))
+ ->toHttpValue()
+;
+
+ // both will return 'b=?0, a=(bar "42" 42 42.0), c=@1671800423
+```
+
+Of course, it is possible to mix both notations.
+
+#### Lists
+
+To create `OuterList` and `InnerList` instances you can use the `new` named constructor
+which takes a single variadic parameter `$members`:
+
+```php
+use Bakame\Http\StructuredFields\InnerList;
+use Bakame\Http\StructuredFields\ByteSequence;
+
+$list = InnerList::new(
+ ByteSequence::fromDecoded('Hello World'),
+ 42.0,
+ 42
+);
+
+echo $list->toHttpValue(); //'(:SGVsbG8gV29ybGQ=: 42.0 42)'
+echo $list; //'(:SGVsbG8gV29ybGQ=: 42.0 42)'
+```
+
+Once again, the builder pattern can be used via a combination of the `new`
+named constructor and the use any of the following modifying methods.
+
+```php
+$list->unshift(...$members): static;
+$list->push(...$members): static;
+$list->insert(int $key, ...$members): static;
+$list->replace(int $key, $member): static;
+$list->remove(int ...$key): static;
+```
+
+as shown below
+
+```php
+use Bakame\Http\StructuredFields\ByteSequence;
+use Bakame\Http\StructuredFields\InnerList;
+
+$list = InnerList::new()
+ ->unshift('42')
+ ->push(42)
+ ->insert(1, 42.0)
+ ->replace(0, ByteSequence::fromDecoded('Hello World'));
+
+echo $list->toHttpValue(); //'(:SGVsbG8gV29ybGQ=: 42.0 42)'
+echo $list; //'(:SGVsbG8gV29ybGQ=: 42.0 42)'
+```
+
+
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', new DateTime('2024-01-01 12:33:45')],
+ ['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
+methods are added:
+
+```php
+use Bakame\Http\StructuredFields\ByteSequence;
+use Bakame\Http\StructuredFields\InnerList;
+use Bakame\Http\StructuredFields\Item;
+use Bakame\Http\StructuredFields\Token;
+
+//@type SfItemInput ByteSequence|Token|DateTimeInterface|string|int|float|bool
+
+Item::fromAssociative(SfItemInput $value, Parameters|iterable $parameters): self;
+Item::fromPair(array{0:SfItemInput, 1:Parameters|iterable} $pair): self;
+
+InnerList::fromAssociative(iterable $value, Parameters|iterable $parameters): self;
+InnerList::fromPair(array{0:iterable, Parameters|iterable} $pair): self;
+```
+
+The following example illustrate how to use those methods:
+
+```php
+use Bakame\Http\StructuredFields\Dictionary;
+use Bakame\Http\StructuredFields\Item;
+
+echo Item::fromAssociative(
+ Token::fromString('bar'),
+ ['baz' => 42]
+ )->toHttpValue(), PHP_EOL;
+
+echo Item::fromPair([
+ Token::fromString('bar'),
+ [['baz', 42]],
+ ])->toHttpValue(), PHP_EOL;
+
+//both methods return `bar;baz=42`
+```
+
+Both objects provide additional modifying methods to help deal with parameters.
+You can attach and update the associated `Parameters` instance using the following methods.
+
+```php
+$field->addParameter(string $key, mixed $value): static;
+$field->appendParameter(string $key, mixed $value): static;
+$field->prependParameter(string $key, mixed $value): static;
+$field->withoutParameters(string ...$keys): static; // this method is deprecated as of version 1.1 use withoutParametersByKeys instead
+$field->withoutAnyParameter(): static;
+$field->withParameters(Parameters $parameters): static;
+```
+Since version `1.1` it is also possible to use the index of each member to perform additional
+modifications.
+
+```php
+$field->pushParameters(array ...$pairs): static
+$field->unshiftParameters(array ...$pairs): static
+$field->insertParameters(int $index, array ...$pairs): static
+$field->replaceParameter(int $index, array $pair): static
+$field->withoutParametersByKeys(string ...$keys): static
+$field->withoutParametersByIndices(int ...$indices): static
+```
+
+The `$pair` parameter is a tuple (ie: an array as list with exactly two members) where:
+
+- the first array member is the parameter `$key`
+- the second array member is the parameter `$value`
+
+
The return value will be the parent class an NOT a Parameters instance
+
+```php
+use Bakame\Http\StructuredFields\InnerList;
+use Bakame\Http\StructuredFields\Item;
+
+echo InnerList::new('foo', 'bar')
+ ->addParameter('expire', Item::fromDateString('+30 minutes'))
+ ->addParameter('path', '/')
+ ->addParameter('max-age', 2500)
+ ->toHttpValue();
+
+echo InnerList::new('foo', 'bar')
+ ->pushParameter(
+ ['expire', Item::fromDateString('+30 minutes')],
+ ['path', '/'],
+ ['max-age', 2500],
+ )
+ ->toHttpValue();
+
+// both flow return the InnerList HTTP value
+// ("foo" "bar");expire=@1681538756;path="/";max-age=2500
+```
+
+### Advance parsing usage
+
+Starting with version `1.1` the internal parser has been made public in order to allow:
+
+- clearer decoupling between parsing and objet building
+- different parsers implementations
+- improve the package usage in testing.
+
+Each `fromHttpValue` method signature has been updated to take a second optional argument
+that represents the parser interface to use in order to allow parsing of the HTTP string
+representation value.
+
+By default, if no parser is provided, the package will default to use the package `Parser` class,
+
+```php
+Item::fromHttpValue(Stringable|string $httpValue, ItemParser $parser = new Parser()): Item;
+InnerList::fromHttpValue(Stringable|string $httpValue, InnerListParser $parser = new Parser()): InnerList;
+Dictionary::fromHttpValue(Stringable|string $httpValue, DictionaryParser $parser = new Parser()): Dictionary;
+OuterList::fromHttpValue(Stringable|string $httpValue, ListParser $parser = new Parser()): OuterList;
+Parameters::fromHttpValue(Stringable|string $httpValue, ParametersParser $parser = new Parser()): Parameters;
+```
+
+The `Parser` class exposes the following method each belonging to a different contract or interface.
+
+```php
+Parser::parseValue(Stringable|string $httpValue): ByteSequence|Token|DateTimeImmutable|string|int|float|bool;
+Parser::parseItem(Stringable|string $httpValue): array;
+Parser::parseParameters(Stringable|string $httpValue): array;
+Parser::parseInnerList(Stringable|string $httpValue): array;
+Parser::parseList(Stringable|string $httpValue): array;
+Parser::parseDictionary(Stringable|string $httpValue): array;
+```
+
+Once instantiated, calling one of the above listed method is straightforward:
+
+```php
+use Bakame\Http\StructuredFields\Parser;
+
+$parser = new Parser();
+$parser->parseValue('text/csv'); //returns Token::fromString('text/csv')
+$parser->parseItem('@1234567890;file=24');
+//returns an array
+// [
+// new DateTimeImmutable('@1234567890'),
+// ['file' => 24],
+// ]
+```
+
+
While the provided default Parser class implements all these methods you are
+free to only implement the methods you need.
diff --git a/docs/1.0/index.md b/docs/1.0/index.md
new file mode 100644
index 0000000..e35644f
--- /dev/null
+++ b/docs/1.0/index.md
@@ -0,0 +1,52 @@
+---
+layout: default
+title: Overview
+---
+
+# Overview
+
+`bakame/http-structured-fields` is a framework-agnostic PHP library that allows you to parse, serialize
+create and update HTTP Structured Fields in PHP according to the [RFC8941](https://www.rfc-editor.org/rfc/rfc8941.html).
+
+Once installed you will be able to do the following:
+
+```php
+use Bakame\Http\StructuredFields\DataType;
+use Bakame\Http\StructuredFields\Token;
+
+//1 - parsing an Accept Header
+$fieldValue = 'text/html, application/xhtml+xml, application/xml;q=0.9, image/webp, */*;q=0.8';
+$field = DataType::List->parse($fieldValue);
+$field[2]->value()->toString(); // returns 'application/xml'
+$field[2]->parameter('q'); // returns (float) 0.9
+$field[0]->value()->toString(); // returns 'text/html'
+$field[0]->parameter('q'); // returns null
+
+//2 - building a retrofit Cookie Header
+echo DataType::List->serialize([
+ [
+ ['foo', 'bar'],
+ [
+ ['expire', new DateTimeImmutable('2023-04-14 20:32:08')],
+ ['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
+```
+
+## System Requirements
+
+**PHP >= 8.1** is required but the latest stable version of PHP is recommended.
+
+## Installation
+
+Use composer:
+
+```
+composer require bakame/http-structured-fields
+```
diff --git a/docs/containers.md b/docs/2.0/containers.md
similarity index 90%
rename from docs/containers.md
rename to docs/2.0/containers.md
index 9fdfa78..bc1c663 100644
--- a/docs/containers.md
+++ b/docs/2.0/containers.md
@@ -1,4 +1,5 @@
---
+layout: default
title: The Structured Field containers Data Types
order: 5
---
@@ -29,8 +30,10 @@ $permissions->isNotEmpty():; // returns true
$permissions->isEmpty(); // returns false
```
-> [!IMPORTANT]
-> The `getByIndex` method will throw an `InvalidOffset` exception if no member exists for the given `$offset`.
+
+The getByIndex method will throw an InvalidOffset exception if no
+member exists for the given $offset.
+
Here's an example with a `List` container:
@@ -46,10 +49,11 @@ $permissions->isNotEmpty():; // returns true
$permissions->isEmpty(); // returns false
```
-> [!IMPORTANT]
-> For ordered maps, the `getByIndex` method returns a list containing exactly 2 entries.
-> The first entry is the member key, the second entry is the member value.
-> For lists, the method directly returns the value.
+
+For ordered maps, the getByIndex method returns a list containing exactly 2 entries.
+The first entry is the member key, the second entry is the member value. For lists, the method
+directly returns the value.
+
To avoid invalid states, `ArrayAccess` modifying methods throw a `ForbiddenOperation`
if you try to use them on any container object:
@@ -61,9 +65,10 @@ $permissions['a'] = 23 // triggers a ForbiddenOperation exception
unset($permissions['a']); // triggers a ForbiddenOperation exception
```
-> [!IMPORTANT]
-> For ordered map the ArrayAccess interface will use the member name
-> whereas for lists the interface will use the member index.
+
+For ordered map the ArrayAccess interface will use the member name
+whereas for lists the interface will use the member index.
+
The `Dictionary` and `Parameters` classes also allow accessing their members as value using their name:
@@ -83,12 +88,16 @@ $permissions->indexByKey('foobar'): // returns null because there's no member wi
$permissions->indexByKey('geolocation'): // returns 1
```
-> [!IMPORTANT]
-> The `getByKey` method will throw an `InvalidOffset` exception if no member exists for the given `$offset`.
+
+The getByKey method will throw an InvalidOffset exception if no
+member exists for the given $offset.
+
-> [!TIP]
-> The `ArrayAccess` interface proxy the result from `getByIndex` and `hasIndices` with `OuterList` and `InnerList`.
-> The `ArrayAccess` interface proxy the result from `getByNKey` and `hasKeys` with `Dictionary` and `Parameters`.
+
+
+
The ArrayAccess interface proxy the result from getByIndex and hasIndices with OuterList and InnerList.
+
The ArrayAccess interface proxy the result from getByKey and hasKeys with Dictionary and Parameters.
+
### Accessing the parameters values
@@ -99,9 +108,10 @@ On the other hand if you already have a `Parameters` instance you can use the
`valueByKey` and `valueByIndex` methods to directly access the value from a single
parameter.
-> [!TIP]
-> The `parameterByKey` proxy the result from `valueByKey`.
-> The `parameterByIndex` proxy the result from `valueByIndex`.
+
+
The parameterByKey proxy the result from valuerByKey.
+
The parameterByIndex proxy the result from valuerByIndex.
+
## Building and Updating Containers
@@ -220,8 +230,8 @@ echo $value->toHttpValue(); //b=?0, a=(bar "42" 42 42.0), c=@1671800423
echo $value; //b=?0, a=(bar "42" 42 42.0), c=@1671800423
```
-> [!CAUTION]
-> on duplicate `names` pair values are merged as per RFC logic.
+
on duplicate names pair values are merged as per RFC logic.
+
The following methods `removeByIndices` and/or `removeByKeys` allow removing members
per indices or per names.
@@ -413,8 +423,7 @@ The `$pair` parameter is an array as list with exactly two members where:
- the first array member is the parameter `$key`
- the second array member is the parameter `$value`
-> [!WARNING]
-> The return value will be the parent class an NOT a `Parameters` instance
+
The return value will be the parent class an NOT a Parameters` instance
```php
use Bakame\Http\StructuredFields\InnerList;
@@ -455,5 +464,3 @@ Item::new($cache->name)
->when(null !== $cache->key, fn (Item $item) => $item->appendParameter('key', $cache->key))
->when(null !== $cache->detail, fn (Item $item) => $item->appendParameter('detail', $cache->detail));
```
-
-← [Accessing Field Values](field-values.md) | [Validation](validation.md) →
diff --git a/docs/extensions.md b/docs/2.0/extensions.md
similarity index 97%
rename from docs/extensions.md
rename to docs/2.0/extensions.md
index b488109..31e37cc 100644
--- a/docs/extensions.md
+++ b/docs/2.0/extensions.md
@@ -1,4 +1,5 @@
---
+layout: default
title: Interacting with the PHP ecosystem
order: 7
---
@@ -100,5 +101,3 @@ contract.
To show how this can be achieved you can check the codebase from [HTTP Cache Status](https://github.com/bakame-php/http-cache-status)
which uses the interface. Of note by using this interface you can completely hide the presence of
this package to your end users if needed.
-
-← [Validation](validation.md) | [Upgrading to v2.0](migration.md) →
diff --git a/docs/field-values.md b/docs/2.0/field-values.md
similarity index 90%
rename from docs/field-values.md
rename to docs/2.0/field-values.md
index f458cb9..9e96b5e 100644
--- a/docs/field-values.md
+++ b/docs/2.0/field-values.md
@@ -1,4 +1,5 @@
---
+layout: default
title: Accessing the HTTP field values
order: 4
---
@@ -21,10 +22,11 @@ or provides a class based alternative. The table below summarizes the value type
| Date | class `DateTimeImmutable` | `Type::Date` | `date` | RFC9651 |
| DisplayString | class `DisplayString` | `Type::DisplayString` | `displaystring` | RFC9651 |
-> [!WARNING]
-> The translation to PHP native type does not mean that all PHP values are usable. For instance, in the
-> following example, what is considered to be a valid string in PHP is not considered as compliant
-> to the string type according to the RFC.
+
+The translation to PHP native type does not mean that all PHP values are usable. For instance, in the
+following example, what is considered to be a valid string in PHP is not considered as compliant
+to the string type according to the RFC.
+
```php
Item::fromString("https://a.bébé.com");
@@ -33,9 +35,10 @@ Item::fromString("https://a.bébé.com");
// contain UTF-8 characters
```
-> [!NOTE]
-> The `Date` and `DisplayString` types were added in the accepted RFC9651
-> but are not part of the obsolete RFC8941 specification.
+
+The Date and DisplayString types were added in the accepted RFC9651
+but are not part of the obsolete RFC8941 specification.
+
The Enum `Type` list all available types and can be used to determine the RFC type
corresponding to a PHP structure using the `Type::fromVariable` static method.
@@ -113,13 +116,11 @@ $byte->type(); // returns Type::Byte
$displayString->type(); // returns Type::DisplayString
```
-> [!WARNING]
-> The classes DO NOT expose the `Stringable` interface to help distinguish
-> them from the string type or a stringable object
+
The classes DO NOT expose the Stringable interface to help distinguish
+them from the string type or a stringable object
+
-> [!IMPORTANT]
-> Values are not directly accessible. They can only be retrieved from an Item
-> Data type.
+
Values are not directly accessible. They can only be retrieved from an Item Data type.
## The Item Data Type
@@ -195,5 +196,3 @@ By default, you can access the member `Item` of a parameters using the following
It is possible to alter and modify the `Parameters` attached to an `Item` but this will be explored in
the next section about the containers.
-
-← [Parsing and Serializing](parsing-serializing.md) | [Containers](containers.md) →
diff --git a/docs/2.0/index.md b/docs/2.0/index.md
new file mode 100644
index 0000000..727c2a0
--- /dev/null
+++ b/docs/2.0/index.md
@@ -0,0 +1,28 @@
+---
+layout: default
+title: Installation
+---
+
+# Installation
+
+## System Requirements
+
+**PHP >= 8.1** is required but the latest stable version of PHP is recommended.
+
+## Installation
+
+Use composer:
+
+```
+composer require bakame/http-structured-fields:^2.0
+```
+
+## Foreword
+
+
+While this package parses and serializes HTTP field value, it does not validate its content
+against any conformance rule out of the box. You are still required to perform such a
+compliance check against the constraints of the corresponding field. While Content
+validation is still possible and highly encouraged when using this library. Because
+of the wide variety of HTTP fields it can not be made mandatory.
+
diff --git a/docs/parsing-serializing.md b/docs/2.0/parsing-serializing.md
similarity index 98%
rename from docs/parsing-serializing.md
rename to docs/2.0/parsing-serializing.md
index 01dc9ab..2d773fc 100644
--- a/docs/parsing-serializing.md
+++ b/docs/2.0/parsing-serializing.md
@@ -1,4 +1,5 @@
---
+layout: default
title: Parsing and Serializing HTTP Fields
order: 3
---
@@ -164,5 +165,3 @@ The `toHttpValue` method applies by default all the normalizations recommended b
> [!TIP]
> This is the mechanism used by the `DataType::serialize` method. Once the HTTP Structured
> Field has been created, the method calls its `toHttpValue` method.
-
-← [Basic Usage](basic-usage.md) | [Accessing Field Values](field-values.md) →
diff --git a/docs/basic-usage.md b/docs/2.0/rfc-usage.md
similarity index 85%
rename from docs/basic-usage.md
rename to docs/2.0/rfc-usage.md
index 97bc02f..974f5fb 100644
--- a/docs/basic-usage.md
+++ b/docs/2.0/rfc-usage.md
@@ -1,11 +1,11 @@
---
-title: Basic usage
-order: 2
+layout: default
+title: RFC Usage
---
-# Basic Usage
+# RFC Usage
-## Parsing the Field
+## Parsing a field
The first way to use the package is to enable HTTP header or HTTP trailer parsing. We will refer to them
as HTTP fields for the rest of the documentation as it is how they are named in the IETF RFC.
@@ -38,13 +38,12 @@ $permissions->isEmpty(); // returns false the dictionary c
echo $permissions; // returns 'picture-in-picture=(), geolocation=(self "https://example.com/"), camera=*'
```
-> [!WARNING]
-> If parsing fails a `SyntaxError` exception is thrown with the information about why it failed.
+
If parsing fails a SyntaxError exception is thrown with the information about why it failed.
-## Creating a new field
+## Serializing a field
Conversely, if you need to quickly create a permission policy HTTP field text representation, the package
-provides the following ways to do so:
+provides the following ways to do by strictly following the RFC:
```php
echo DataType::Dictionary->serialize([
@@ -60,10 +59,9 @@ pairs to respect value position. As such we can turn the iterable construct we h
proper HTTP field text representation by applying the serialization mechanism described in
the RFC.
-While field building may look overwhelming, at first, it follows a fully described and tested
-process that the package can simplify for you once you read the documentation.
+While field building may look overwhelming, at first, it follows the fully described and tested
+process from the RFC. The package exposes numerous methods to simplify this process for you.
The goal of the examples are to show that even without dwelling too much into the ins and out
-of the package you can easily and quickly parse or serialize compliant fields in PHP.
-
-← [Intro](index.md) | [Parsing and Serializing](parsing-serializing.md) →
+of the package you can easily and quickly parse or serialize compliant fields in PHP base solely
+on the RFC.
diff --git a/docs/migration.md b/docs/2.0/upgrading.md
similarity index 98%
rename from docs/migration.md
rename to docs/2.0/upgrading.md
index 31e1473..91e1586 100644
--- a/docs/migration.md
+++ b/docs/2.0/upgrading.md
@@ -1,4 +1,5 @@
---
+layout: default
title: Migrating from version 1.x
order: 8
---
@@ -149,5 +150,3 @@ OuterList::fromPairs([
> [!NOTE]
> The v1 syntax is still supported.
-
-← [Extending the package functionalities](extensions.md) | [Intro](index.md) →
diff --git a/docs/validation.md b/docs/2.0/validation.md
similarity index 88%
rename from docs/validation.md
rename to docs/2.0/validation.md
index 054d722..13d1362 100644
--- a/docs/validation.md
+++ b/docs/2.0/validation.md
@@ -1,4 +1,5 @@
---
+layout: default
title: Structured Field validation
order: 6
---
@@ -37,8 +38,7 @@ $field->last()->value(); // returns 12.8
So far so good, the field is successfully parsed by the package.
-> [!NOTE]
-> The field would fail parsing with the obsolete RFC8941 definition
+
The field would fail parsing with the obsolete RFC8941 definition.
### Validating each entry separately
@@ -131,9 +131,10 @@ $value = $member
// new Violation("The value 'foo' failed the RFC validation.");
```
-> [!NOTE]
-> we used `mixed` as parameter type for convenience but the effective parameter type should be
-> `Byte|Token|DisplayString|DateTimeImmutable|string|int|float|bool`
+
+we used mixed as parameter type for convenience but the effective parameter type should be
+Byte|Token|DisplayString|DateTimeImmutable|string|int|float|bool
+
### Validating the Item parameters.
@@ -155,11 +156,8 @@ if (!$member->parameters()->allowedKeys(['location', 'longitude', 'latitude', 'd
}
```
-> [!TIP]
-> The `Dictionary` class also exposes an `allowedKeys` method which behave the same way.
-
-> [!WARNING]
-> if the parameters container is empty no error will be triggered
+
The Dictionary class also exposes an allowedKeys method which behave the same way.
+
if the parameters container is empty no error will be triggered
### Validating single parameters
@@ -182,10 +180,11 @@ $member->parameterByKey(
);
```
-> [!NOTE]
-> `parameterByIndex` uses the same parameter only the callback parameter are
-> different as a second parameter the string name is added to the callback
-> for validation purpose.
+
+parameterByIndex uses the same parameter only the callback parameter are
+different as a second parameter the string name is added to the callback
+for validation purpose.
+
### Validating the complete Parameter container
@@ -322,12 +321,14 @@ if ($validation->isSucces()) {
}
```
-> [!NOTE]
-> If we only had validated the `longitude` parameter. it would have been
-> the only one present in the returned data.
+
+If we only had validated the `longitude` parameter. it would have been
+the only one present in the returned data.
+
-> [!NOTE]
-> If we only use the `filterByCriteria` method the full parameter data is returned.
+
+If we only use the `filterByCriteria` method the full parameter data is returned.
+
A `filterByIndices` method exists and behave exactly as the `filterByKeys` method.
There are two differences when it is used:
@@ -345,9 +346,11 @@ if ($validation->isSucces()) {
}
```
-> [!IMPORTANT]
-> Both methods are mutually exclusive if you use them both, the last one used will
-> be the one which format the returned data.
+
+Both methods are mutually exclusive if you use them both, the last one used will
+be the one which format the returned data.
+
+
### Validating the full Item
@@ -417,19 +420,21 @@ The `ParametersValidator` and the `ItemValidator` are invokable, so we can use t
directly with the `getBy*` methods. So once you have configured your validator in a
class it becomes easier to reuse it to validate your data.
-> [!NOTE]
-> When used as invokable the validators return `true` on success and the aggregated
-> error messages as string on error.
+
+When used as invokable the validators return true on success and the aggregated
+error messages as string on error.
+
-> [!TIP]
-> A best practice is to move all this validation definition in its own class, and use that
-> class instead to ease maintenance and testing.
+
+A best practice is to move all this validation definition in its own class, and use that
+class instead to ease maintenance and testing.
+
-> [!TIP]
-> Once you have a specific class to validate a single entry for your list or dictionary it is
-> easy to validate all the container using either the `map`, `filter` or the `reduce` method
-> associated with each container.
+
+Once you have a specific class to validate a single entry for your list or dictionary it is
+easy to validate all the container using either the map, filter
+or the recude method associated with each container.
+
-To show how this can be achieved you can check the codebase from [HTTP Cache Status](https://github.com/bakame-php/http-cache-status)
-← [Containers](containers.md) | [Extending the package functionalities](extensions.md) →
+To show how this can be achieved you can check the codebase from [HTTP Cache Status](https://github.com/bakame-php/http-cache-status)
diff --git a/docs/Gemfile b/docs/Gemfile
deleted file mode 100644
index 719d7fd..0000000
--- a/docs/Gemfile
+++ /dev/null
@@ -1,8 +0,0 @@
-source 'https://rubygems.org'
-
-group :jekyll_plugins do
- gem "jekyll-gfm-admonitions"
- gem "kramdown-parser-gfm"
- gem "minima"
- gem "jekyll-seo-tag"
-end
diff --git a/docs/_config.yml b/docs/_config.yml
index dd8eab5..db458d1 100644
--- a/docs/_config.yml
+++ b/docs/_config.yml
@@ -1,5 +1,5 @@
---
permalink: pretty
+baseurl: /http-structured-fields
plugins:
- - jekyll-gfm-admonitions
- - jekyll-seo-tag
+ - jekyll-redirect-from
diff --git a/docs/_data/manifest.yml b/docs/_data/manifest.yml
new file mode 100644
index 0000000..8a767b9
--- /dev/null
+++ b/docs/_data/manifest.yml
@@ -0,0 +1,4 @@
+{
+ "docs.css": "/styles.0004.css",
+ "docs.js": "/scripts.0002.js"
+}
diff --git a/docs/_data/menu.yml b/docs/_data/menu.yml
new file mode 100644
index 0000000..3eb7eac
--- /dev/null
+++ b/docs/_data/menu.yml
@@ -0,0 +1,20 @@
+version:
+ '2.0':
+ Getting Started:
+ Installation: '/2.0/'
+ Upgrading from 1.x: '/2.0/upgrading/'
+ Documentation:
+ RFC Usage: '/2.0/rfc-usage/'
+ Parsing & Serializing: '/2.0/parsing-serializing/'
+ Field Values: '/2.0/field-values/'
+ Containers: '/2.0/containers/'
+ Validation: '/2.0/validation/'
+ Extending: '/2.0/extensions/'
+ '1.0':
+ Getting Started:
+ Overview: '/1.0/'
+ Documentation: '/1.0/documentation/'
+upgrading:
+ 'Upgrading guide':
+ Release Notes: '/releases/'
+ From 1.x to 2.x: '/2.0/upgrading/'
diff --git a/docs/_data/project.yml b/docs/_data/project.yml
new file mode 100644
index 0000000..94dc5f7
--- /dev/null
+++ b/docs/_data/project.yml
@@ -0,0 +1,10 @@
+# Documentation website
+title: HTTP Structured Fields
+tagline: "Work with HTTP Structured Fields in PHP."
+description: "A framework-agnostic PHP library that allows you to parse, serialize, create, update and validate HTTP Structured Fields in PHP following RFC9651 and RFC8941."
+# Github repository
+package: 'bakame/http-structured-fields'
+repository_url: 'https://github.com/bakame-php/http-structured-fields'
+release_major: '2.0'
+version: 2.0.0
+support: 'Once a new major version is released, the previous stable release remains supported for six more months with patches and security fixes.'
diff --git a/docs/_layouts/default.html b/docs/_layouts/default.html
new file mode 100644
index 0000000..1eb650a
--- /dev/null
+++ b/docs/_layouts/default.html
@@ -0,0 +1,129 @@
+{% assign version = page.url | remove_first: "/" | split: "/" | first %}
+{% assign upgrading = false %}
+{% if version == '' or version == 'releases' %}
+ {% assign version = site.data.project.releases.current.version %}
+ {% assign upgrading = true %}
+{% endif %}
+{% capture version_home %}/{{ version }}/{% endcapture %}
+
+
+
+
+
+
+
+
+ {{ page.title }} - {{ site.data.project.title }}
+
+
+
+
+
+
+
+